github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/builtin/providers/aws/resource_aws_lambda_function.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "log" 7 "time" 8 9 "github.com/aws/aws-sdk-go/aws" 10 "github.com/aws/aws-sdk-go/aws/awserr" 11 "github.com/aws/aws-sdk-go/service/lambda" 12 "github.com/mitchellh/go-homedir" 13 14 "errors" 15 16 "github.com/hashicorp/terraform/helper/resource" 17 "github.com/hashicorp/terraform/helper/schema" 18 ) 19 20 const awsMutexLambdaKey = `aws_lambda_function` 21 22 func resourceAwsLambdaFunction() *schema.Resource { 23 return &schema.Resource{ 24 Create: resourceAwsLambdaFunctionCreate, 25 Read: resourceAwsLambdaFunctionRead, 26 Update: resourceAwsLambdaFunctionUpdate, 27 Delete: resourceAwsLambdaFunctionDelete, 28 29 Importer: &schema.ResourceImporter{ 30 State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 31 d.Set("function_name", d.Id()) 32 return []*schema.ResourceData{d}, nil 33 }, 34 }, 35 36 Schema: map[string]*schema.Schema{ 37 "filename": { 38 Type: schema.TypeString, 39 Optional: true, 40 ConflictsWith: []string{"s3_bucket", "s3_key", "s3_object_version"}, 41 }, 42 "s3_bucket": { 43 Type: schema.TypeString, 44 Optional: true, 45 ConflictsWith: []string{"filename"}, 46 }, 47 "s3_key": { 48 Type: schema.TypeString, 49 Optional: true, 50 ConflictsWith: []string{"filename"}, 51 }, 52 "s3_object_version": { 53 Type: schema.TypeString, 54 Optional: true, 55 ConflictsWith: []string{"filename"}, 56 }, 57 "description": { 58 Type: schema.TypeString, 59 Optional: true, 60 }, 61 "function_name": { 62 Type: schema.TypeString, 63 Required: true, 64 ForceNew: true, 65 }, 66 "handler": { 67 Type: schema.TypeString, 68 Required: true, 69 }, 70 "memory_size": { 71 Type: schema.TypeInt, 72 Optional: true, 73 Default: 128, 74 }, 75 "role": { 76 Type: schema.TypeString, 77 Required: true, 78 }, 79 "runtime": { 80 Type: schema.TypeString, 81 Required: true, 82 ForceNew: true, 83 ValidateFunc: validateRuntime, 84 }, 85 "timeout": { 86 Type: schema.TypeInt, 87 Optional: true, 88 Default: 3, 89 }, 90 "publish": { 91 Type: schema.TypeBool, 92 Optional: true, 93 Default: false, 94 }, 95 "version": { 96 Type: schema.TypeString, 97 Computed: true, 98 }, 99 "vpc_config": { 100 Type: schema.TypeList, 101 Optional: true, 102 ForceNew: true, 103 Elem: &schema.Resource{ 104 Schema: map[string]*schema.Schema{ 105 "subnet_ids": { 106 Type: schema.TypeSet, 107 Required: true, 108 ForceNew: true, 109 Elem: &schema.Schema{Type: schema.TypeString}, 110 Set: schema.HashString, 111 }, 112 "security_group_ids": { 113 Type: schema.TypeSet, 114 Required: true, 115 ForceNew: true, 116 Elem: &schema.Schema{Type: schema.TypeString}, 117 Set: schema.HashString, 118 }, 119 "vpc_id": { 120 Type: schema.TypeString, 121 Computed: true, 122 }, 123 }, 124 }, 125 }, 126 "arn": { 127 Type: schema.TypeString, 128 Computed: true, 129 }, 130 "qualified_arn": { 131 Type: schema.TypeString, 132 Computed: true, 133 }, 134 "last_modified": { 135 Type: schema.TypeString, 136 Computed: true, 137 }, 138 "source_code_hash": { 139 Type: schema.TypeString, 140 Optional: true, 141 Computed: true, 142 }, 143 "environment": { 144 Type: schema.TypeList, 145 Optional: true, 146 MaxItems: 1, 147 Elem: &schema.Resource{ 148 Schema: map[string]*schema.Schema{ 149 "variables": { 150 Type: schema.TypeMap, 151 Optional: true, 152 Elem: schema.TypeString, 153 }, 154 }, 155 }, 156 }, 157 158 "kms_key_arn": { 159 Type: schema.TypeString, 160 Optional: true, 161 ValidateFunc: validateArn, 162 }, 163 }, 164 } 165 } 166 167 // resourceAwsLambdaFunction maps to: 168 // CreateFunction in the API / SDK 169 func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) error { 170 conn := meta.(*AWSClient).lambdaconn 171 172 functionName := d.Get("function_name").(string) 173 iamRole := d.Get("role").(string) 174 175 log.Printf("[DEBUG] Creating Lambda Function %s with role %s", functionName, iamRole) 176 177 filename, hasFilename := d.GetOk("filename") 178 s3Bucket, bucketOk := d.GetOk("s3_bucket") 179 s3Key, keyOk := d.GetOk("s3_key") 180 s3ObjectVersion, versionOk := d.GetOk("s3_object_version") 181 182 if !hasFilename && !bucketOk && !keyOk && !versionOk { 183 return errors.New("filename or s3_* attributes must be set") 184 } 185 186 var functionCode *lambda.FunctionCode 187 if hasFilename { 188 // Grab an exclusive lock so that we're only reading one function into 189 // memory at a time. 190 // See https://github.com/hashicorp/terraform/issues/9364 191 awsMutexKV.Lock(awsMutexLambdaKey) 192 defer awsMutexKV.Unlock(awsMutexLambdaKey) 193 file, err := loadFileContent(filename.(string)) 194 if err != nil { 195 return fmt.Errorf("Unable to load %q: %s", filename.(string), err) 196 } 197 functionCode = &lambda.FunctionCode{ 198 ZipFile: file, 199 } 200 } else { 201 if !bucketOk || !keyOk { 202 return errors.New("s3_bucket and s3_key must all be set while using S3 code source") 203 } 204 functionCode = &lambda.FunctionCode{ 205 S3Bucket: aws.String(s3Bucket.(string)), 206 S3Key: aws.String(s3Key.(string)), 207 } 208 if versionOk { 209 functionCode.S3ObjectVersion = aws.String(s3ObjectVersion.(string)) 210 } 211 } 212 213 params := &lambda.CreateFunctionInput{ 214 Code: functionCode, 215 Description: aws.String(d.Get("description").(string)), 216 FunctionName: aws.String(functionName), 217 Handler: aws.String(d.Get("handler").(string)), 218 MemorySize: aws.Int64(int64(d.Get("memory_size").(int))), 219 Role: aws.String(iamRole), 220 Runtime: aws.String(d.Get("runtime").(string)), 221 Timeout: aws.Int64(int64(d.Get("timeout").(int))), 222 Publish: aws.Bool(d.Get("publish").(bool)), 223 } 224 225 if v, ok := d.GetOk("vpc_config"); ok { 226 config, err := validateVPCConfig(v) 227 if err != nil { 228 return err 229 } 230 231 if config != nil { 232 var subnetIds []*string 233 for _, id := range config["subnet_ids"].(*schema.Set).List() { 234 subnetIds = append(subnetIds, aws.String(id.(string))) 235 } 236 237 var securityGroupIds []*string 238 for _, id := range config["security_group_ids"].(*schema.Set).List() { 239 securityGroupIds = append(securityGroupIds, aws.String(id.(string))) 240 } 241 242 params.VpcConfig = &lambda.VpcConfig{ 243 SubnetIds: subnetIds, 244 SecurityGroupIds: securityGroupIds, 245 } 246 } 247 } 248 249 if v, ok := d.GetOk("environment"); ok { 250 environments := v.([]interface{}) 251 environment, ok := environments[0].(map[string]interface{}) 252 if !ok { 253 return errors.New("At least one field is expected inside environment") 254 } 255 256 if environmentVariables, ok := environment["variables"]; ok { 257 variables := readEnvironmentVariables(environmentVariables.(map[string]interface{})) 258 259 params.Environment = &lambda.Environment{ 260 Variables: aws.StringMap(variables), 261 } 262 } 263 } 264 265 if v, ok := d.GetOk("kms_key_arn"); ok { 266 params.KMSKeyArn = aws.String(v.(string)) 267 } 268 269 // IAM profiles can take ~10 seconds to propagate in AWS: 270 // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console 271 // Error creating Lambda function: InvalidParameterValueException: The role defined for the task cannot be assumed by Lambda. 272 err := resource.Retry(10*time.Minute, func() *resource.RetryError { 273 _, err := conn.CreateFunction(params) 274 if err != nil { 275 log.Printf("[ERROR] Received %q, retrying CreateFunction", err) 276 if awserr, ok := err.(awserr.Error); ok { 277 if awserr.Code() == "InvalidParameterValueException" { 278 log.Printf("[DEBUG] InvalidParameterValueException creating Lambda Function: %s", awserr) 279 return resource.RetryableError(awserr) 280 } 281 } 282 log.Printf("[DEBUG] Error creating Lambda Function: %s", err) 283 return resource.NonRetryableError(err) 284 } 285 return nil 286 }) 287 if err != nil { 288 return fmt.Errorf("Error creating Lambda function: %s", err) 289 } 290 291 d.SetId(d.Get("function_name").(string)) 292 293 return resourceAwsLambdaFunctionRead(d, meta) 294 } 295 296 // resourceAwsLambdaFunctionRead maps to: 297 // GetFunction in the API / SDK 298 func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) error { 299 conn := meta.(*AWSClient).lambdaconn 300 301 log.Printf("[DEBUG] Fetching Lambda Function: %s", d.Id()) 302 303 params := &lambda.GetFunctionInput{ 304 FunctionName: aws.String(d.Get("function_name").(string)), 305 } 306 307 getFunctionOutput, err := conn.GetFunction(params) 308 if err != nil { 309 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" && !d.IsNewResource() { 310 d.SetId("") 311 return nil 312 } 313 return err 314 } 315 316 // getFunctionOutput.Code.Location is a pre-signed URL pointing at the zip 317 // file that we uploaded when we created the resource. You can use it to 318 // download the code from AWS. The other part is 319 // getFunctionOutput.Configuration which holds metadata. 320 321 function := getFunctionOutput.Configuration 322 // TODO error checking / handling on the Set() calls. 323 d.Set("arn", function.FunctionArn) 324 d.Set("description", function.Description) 325 d.Set("handler", function.Handler) 326 d.Set("memory_size", function.MemorySize) 327 d.Set("last_modified", function.LastModified) 328 d.Set("role", function.Role) 329 d.Set("runtime", function.Runtime) 330 d.Set("timeout", function.Timeout) 331 d.Set("kms_key_arn", function.KMSKeyArn) 332 333 config := flattenLambdaVpcConfigResponse(function.VpcConfig) 334 log.Printf("[INFO] Setting Lambda %s VPC config %#v from API", d.Id(), config) 335 vpcSetErr := d.Set("vpc_config", config) 336 if vpcSetErr != nil { 337 return fmt.Errorf("Failed setting vpc_config: %s", vpcSetErr) 338 } 339 340 d.Set("source_code_hash", function.CodeSha256) 341 342 if err := d.Set("environment", flattenLambdaEnvironment(function.Environment)); err != nil { 343 log.Printf("[ERR] Error setting environment for Lambda Function (%s): %s", d.Id(), err) 344 } 345 346 // List is sorted from oldest to latest 347 // so this may get costly over time :'( 348 var lastVersion, lastQualifiedArn string 349 err = listVersionsByFunctionPages(conn, &lambda.ListVersionsByFunctionInput{ 350 FunctionName: function.FunctionName, 351 MaxItems: aws.Int64(10000), 352 }, func(p *lambda.ListVersionsByFunctionOutput, lastPage bool) bool { 353 if lastPage { 354 last := p.Versions[len(p.Versions)-1] 355 lastVersion = *last.Version 356 lastQualifiedArn = *last.FunctionArn 357 return true 358 } 359 return false 360 }) 361 if err != nil { 362 return err 363 } 364 365 d.Set("version", lastVersion) 366 d.Set("qualified_arn", lastQualifiedArn) 367 368 return nil 369 } 370 371 func listVersionsByFunctionPages(c *lambda.Lambda, input *lambda.ListVersionsByFunctionInput, 372 fn func(p *lambda.ListVersionsByFunctionOutput, lastPage bool) bool) error { 373 for { 374 page, err := c.ListVersionsByFunction(input) 375 if err != nil { 376 return err 377 } 378 lastPage := page.NextMarker == nil 379 380 shouldContinue := fn(page, lastPage) 381 if !shouldContinue || lastPage { 382 break 383 } 384 } 385 return nil 386 } 387 388 // resourceAwsLambdaFunction maps to: 389 // DeleteFunction in the API / SDK 390 func resourceAwsLambdaFunctionDelete(d *schema.ResourceData, meta interface{}) error { 391 conn := meta.(*AWSClient).lambdaconn 392 393 log.Printf("[INFO] Deleting Lambda Function: %s", d.Id()) 394 395 params := &lambda.DeleteFunctionInput{ 396 FunctionName: aws.String(d.Get("function_name").(string)), 397 } 398 399 _, err := conn.DeleteFunction(params) 400 if err != nil { 401 return fmt.Errorf("Error deleting Lambda Function: %s", err) 402 } 403 404 d.SetId("") 405 406 return nil 407 } 408 409 // resourceAwsLambdaFunctionUpdate maps to: 410 // UpdateFunctionCode in the API / SDK 411 func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) error { 412 conn := meta.(*AWSClient).lambdaconn 413 414 d.Partial(true) 415 416 if d.HasChange("filename") || d.HasChange("source_code_hash") || d.HasChange("s3_bucket") || d.HasChange("s3_key") || d.HasChange("s3_object_version") { 417 codeReq := &lambda.UpdateFunctionCodeInput{ 418 FunctionName: aws.String(d.Id()), 419 Publish: aws.Bool(d.Get("publish").(bool)), 420 } 421 422 if v, ok := d.GetOk("filename"); ok { 423 // Grab an exclusive lock so that we're only reading one function into 424 // memory at a time. 425 // See https://github.com/hashicorp/terraform/issues/9364 426 awsMutexKV.Lock(awsMutexLambdaKey) 427 defer awsMutexKV.Unlock(awsMutexLambdaKey) 428 file, err := loadFileContent(v.(string)) 429 if err != nil { 430 return fmt.Errorf("Unable to load %q: %s", v.(string), err) 431 } 432 codeReq.ZipFile = file 433 } else { 434 s3Bucket, _ := d.GetOk("s3_bucket") 435 s3Key, _ := d.GetOk("s3_key") 436 s3ObjectVersion, versionOk := d.GetOk("s3_object_version") 437 438 codeReq.S3Bucket = aws.String(s3Bucket.(string)) 439 codeReq.S3Key = aws.String(s3Key.(string)) 440 if versionOk { 441 codeReq.S3ObjectVersion = aws.String(s3ObjectVersion.(string)) 442 } 443 } 444 445 log.Printf("[DEBUG] Send Update Lambda Function Code request: %#v", codeReq) 446 447 _, err := conn.UpdateFunctionCode(codeReq) 448 if err != nil { 449 return fmt.Errorf("Error modifying Lambda Function Code %s: %s", d.Id(), err) 450 } 451 452 d.SetPartial("filename") 453 d.SetPartial("source_code_hash") 454 d.SetPartial("s3_bucket") 455 d.SetPartial("s3_key") 456 d.SetPartial("s3_object_version") 457 } 458 459 configReq := &lambda.UpdateFunctionConfigurationInput{ 460 FunctionName: aws.String(d.Id()), 461 } 462 463 configUpdate := false 464 if d.HasChange("description") { 465 configReq.Description = aws.String(d.Get("description").(string)) 466 configUpdate = true 467 } 468 if d.HasChange("handler") { 469 configReq.Handler = aws.String(d.Get("handler").(string)) 470 configUpdate = true 471 } 472 if d.HasChange("memory_size") { 473 configReq.MemorySize = aws.Int64(int64(d.Get("memory_size").(int))) 474 configUpdate = true 475 } 476 if d.HasChange("role") { 477 configReq.Role = aws.String(d.Get("role").(string)) 478 configUpdate = true 479 } 480 if d.HasChange("timeout") { 481 configReq.Timeout = aws.Int64(int64(d.Get("timeout").(int))) 482 configUpdate = true 483 } 484 if d.HasChange("kms_key_arn") { 485 configReq.KMSKeyArn = aws.String(d.Get("kms_key_arn").(string)) 486 configUpdate = true 487 } 488 if d.HasChange("environment") { 489 if v, ok := d.GetOk("environment"); ok { 490 environments := v.([]interface{}) 491 environment, ok := environments[0].(map[string]interface{}) 492 if !ok { 493 return errors.New("At least one field is expected inside environment") 494 } 495 496 if environmentVariables, ok := environment["variables"]; ok { 497 variables := readEnvironmentVariables(environmentVariables.(map[string]interface{})) 498 499 configReq.Environment = &lambda.Environment{ 500 Variables: aws.StringMap(variables), 501 } 502 configUpdate = true 503 } 504 } else { 505 configReq.Environment = &lambda.Environment{ 506 Variables: aws.StringMap(map[string]string{}), 507 } 508 configUpdate = true 509 } 510 } 511 512 if configUpdate { 513 log.Printf("[DEBUG] Send Update Lambda Function Configuration request: %#v", configReq) 514 _, err := conn.UpdateFunctionConfiguration(configReq) 515 if err != nil { 516 return fmt.Errorf("Error modifying Lambda Function Configuration %s: %s", d.Id(), err) 517 } 518 d.SetPartial("description") 519 d.SetPartial("handler") 520 d.SetPartial("memory_size") 521 d.SetPartial("role") 522 d.SetPartial("timeout") 523 } 524 d.Partial(false) 525 526 return resourceAwsLambdaFunctionRead(d, meta) 527 } 528 529 // loadFileContent returns contents of a file in a given path 530 func loadFileContent(v string) ([]byte, error) { 531 filename, err := homedir.Expand(v) 532 if err != nil { 533 return nil, err 534 } 535 fileContent, err := ioutil.ReadFile(filename) 536 if err != nil { 537 return nil, err 538 } 539 return fileContent, nil 540 } 541 542 func readEnvironmentVariables(ev map[string]interface{}) map[string]string { 543 variables := make(map[string]string) 544 for k, v := range ev { 545 variables[k] = v.(string) 546 } 547 548 return variables 549 } 550 551 func validateVPCConfig(v interface{}) (map[string]interface{}, error) { 552 configs := v.([]interface{}) 553 if len(configs) > 1 { 554 return nil, errors.New("Only a single vpc_config block is expected") 555 } 556 557 config, ok := configs[0].(map[string]interface{}) 558 559 if !ok { 560 return nil, errors.New("vpc_config is <nil>") 561 } 562 563 // if subnet_ids and security_group_ids are both empty then the VPC is optional 564 if config["subnet_ids"].(*schema.Set).Len() == 0 && config["security_group_ids"].(*schema.Set).Len() == 0 { 565 return nil, nil 566 } 567 568 if config["subnet_ids"].(*schema.Set).Len() == 0 { 569 return nil, errors.New("vpc_config.subnet_ids cannot be empty") 570 } 571 572 if config["security_group_ids"].(*schema.Set).Len() == 0 { 573 return nil, errors.New("vpc_config.security_group_ids cannot be empty") 574 } 575 576 return config, nil 577 } 578 579 func validateRuntime(v interface{}, k string) (ws []string, errors []error) { 580 runtime := v.(string) 581 582 if runtime == lambda.RuntimeNodejs { 583 errors = append(errors, fmt.Errorf( 584 "%s has reached end of life since October 2016 and has been deprecated in favor of %s.", 585 runtime, lambda.RuntimeNodejs43)) 586 } 587 return 588 }