github.com/emc-cmd/terraform@v0.7.8-0.20161101145618-f16309630e7c/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": &schema.Schema{ 38 Type: schema.TypeString, 39 Optional: true, 40 ConflictsWith: []string{"s3_bucket", "s3_key", "s3_object_version"}, 41 }, 42 "s3_bucket": &schema.Schema{ 43 Type: schema.TypeString, 44 Optional: true, 45 ConflictsWith: []string{"filename"}, 46 }, 47 "s3_key": &schema.Schema{ 48 Type: schema.TypeString, 49 Optional: true, 50 ConflictsWith: []string{"filename"}, 51 }, 52 "s3_object_version": &schema.Schema{ 53 Type: schema.TypeString, 54 Optional: true, 55 ConflictsWith: []string{"filename"}, 56 }, 57 "description": &schema.Schema{ 58 Type: schema.TypeString, 59 Optional: true, 60 }, 61 "function_name": &schema.Schema{ 62 Type: schema.TypeString, 63 Required: true, 64 ForceNew: true, 65 }, 66 "handler": &schema.Schema{ 67 Type: schema.TypeString, 68 Required: true, 69 }, 70 "memory_size": &schema.Schema{ 71 Type: schema.TypeInt, 72 Optional: true, 73 Default: 128, 74 }, 75 "role": &schema.Schema{ 76 Type: schema.TypeString, 77 Required: true, 78 }, 79 "runtime": &schema.Schema{ 80 Type: schema.TypeString, 81 Optional: true, 82 ForceNew: true, 83 Default: "nodejs", 84 }, 85 "timeout": &schema.Schema{ 86 Type: schema.TypeInt, 87 Optional: true, 88 Default: 3, 89 }, 90 "publish": &schema.Schema{ 91 Type: schema.TypeBool, 92 Optional: true, 93 Default: false, 94 }, 95 "version": &schema.Schema{ 96 Type: schema.TypeString, 97 Computed: true, 98 }, 99 "vpc_config": &schema.Schema{ 100 Type: schema.TypeList, 101 Optional: true, 102 ForceNew: true, 103 Elem: &schema.Resource{ 104 Schema: map[string]*schema.Schema{ 105 "subnet_ids": &schema.Schema{ 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": &schema.Schema{ 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": &schema.Schema{ 120 Type: schema.TypeString, 121 Computed: true, 122 }, 123 }, 124 }, 125 }, 126 "arn": &schema.Schema{ 127 Type: schema.TypeString, 128 Computed: true, 129 }, 130 "qualified_arn": &schema.Schema{ 131 Type: schema.TypeString, 132 Computed: true, 133 }, 134 "last_modified": &schema.Schema{ 135 Type: schema.TypeString, 136 Computed: true, 137 }, 138 "source_code_hash": &schema.Schema{ 139 Type: schema.TypeString, 140 Optional: true, 141 Computed: true, 142 }, 143 }, 144 } 145 } 146 147 // resourceAwsLambdaFunction maps to: 148 // CreateFunction in the API / SDK 149 func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) error { 150 conn := meta.(*AWSClient).lambdaconn 151 152 functionName := d.Get("function_name").(string) 153 iamRole := d.Get("role").(string) 154 155 log.Printf("[DEBUG] Creating Lambda Function %s with role %s", functionName, iamRole) 156 157 var functionCode *lambda.FunctionCode 158 if v, ok := d.GetOk("filename"); ok { 159 // Grab an exclusive lock so that we're only reading one function into 160 // memory at a time. 161 // See https://github.com/hashicorp/terraform/issues/9364 162 awsMutexKV.Lock(awsMutexLambdaKey) 163 defer awsMutexKV.Unlock(awsMutexLambdaKey) 164 file, err := loadFileContent(v.(string)) 165 if err != nil { 166 return fmt.Errorf("Unable to load %q: %s", v.(string), err) 167 } 168 functionCode = &lambda.FunctionCode{ 169 ZipFile: file, 170 } 171 } else { 172 s3Bucket, bucketOk := d.GetOk("s3_bucket") 173 s3Key, keyOk := d.GetOk("s3_key") 174 s3ObjectVersion, versionOk := d.GetOk("s3_object_version") 175 if !bucketOk || !keyOk { 176 return errors.New("s3_bucket and s3_key must all be set while using S3 code source") 177 } 178 functionCode = &lambda.FunctionCode{ 179 S3Bucket: aws.String(s3Bucket.(string)), 180 S3Key: aws.String(s3Key.(string)), 181 } 182 if versionOk { 183 functionCode.S3ObjectVersion = aws.String(s3ObjectVersion.(string)) 184 } 185 } 186 187 params := &lambda.CreateFunctionInput{ 188 Code: functionCode, 189 Description: aws.String(d.Get("description").(string)), 190 FunctionName: aws.String(functionName), 191 Handler: aws.String(d.Get("handler").(string)), 192 MemorySize: aws.Int64(int64(d.Get("memory_size").(int))), 193 Role: aws.String(iamRole), 194 Runtime: aws.String(d.Get("runtime").(string)), 195 Timeout: aws.Int64(int64(d.Get("timeout").(int))), 196 Publish: aws.Bool(d.Get("publish").(bool)), 197 } 198 199 if v, ok := d.GetOk("vpc_config"); ok { 200 config, err := validateVPCConfig(v) 201 if err != nil { 202 return err 203 } 204 205 if config != nil { 206 var subnetIds []*string 207 for _, id := range config["subnet_ids"].(*schema.Set).List() { 208 subnetIds = append(subnetIds, aws.String(id.(string))) 209 } 210 211 var securityGroupIds []*string 212 for _, id := range config["security_group_ids"].(*schema.Set).List() { 213 securityGroupIds = append(securityGroupIds, aws.String(id.(string))) 214 } 215 216 params.VpcConfig = &lambda.VpcConfig{ 217 SubnetIds: subnetIds, 218 SecurityGroupIds: securityGroupIds, 219 } 220 } 221 } 222 223 // IAM profiles can take ~10 seconds to propagate in AWS: 224 // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console 225 // Error creating Lambda function: InvalidParameterValueException: The role defined for the task cannot be assumed by Lambda. 226 err := resource.Retry(1*time.Minute, func() *resource.RetryError { 227 _, err := conn.CreateFunction(params) 228 if err != nil { 229 log.Printf("[ERROR] Received %q, retrying CreateFunction", err) 230 if awserr, ok := err.(awserr.Error); ok { 231 if awserr.Code() == "InvalidParameterValueException" { 232 log.Printf("[DEBUG] InvalidParameterValueException creating Lambda Function: %s", awserr) 233 return resource.RetryableError(awserr) 234 } 235 } 236 log.Printf("[DEBUG] Error creating Lambda Function: %s", err) 237 return resource.NonRetryableError(err) 238 } 239 return nil 240 }) 241 if err != nil { 242 return fmt.Errorf("Error creating Lambda function: %s", err) 243 } 244 245 d.SetId(d.Get("function_name").(string)) 246 247 return resourceAwsLambdaFunctionRead(d, meta) 248 } 249 250 // resourceAwsLambdaFunctionRead maps to: 251 // GetFunction in the API / SDK 252 func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) error { 253 conn := meta.(*AWSClient).lambdaconn 254 255 log.Printf("[DEBUG] Fetching Lambda Function: %s", d.Id()) 256 257 params := &lambda.GetFunctionInput{ 258 FunctionName: aws.String(d.Get("function_name").(string)), 259 } 260 261 getFunctionOutput, err := conn.GetFunction(params) 262 if err != nil { 263 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" && !d.IsNewResource() { 264 d.SetId("") 265 return nil 266 } 267 return err 268 } 269 270 // getFunctionOutput.Code.Location is a pre-signed URL pointing at the zip 271 // file that we uploaded when we created the resource. You can use it to 272 // download the code from AWS. The other part is 273 // getFunctionOutput.Configuration which holds metadata. 274 275 function := getFunctionOutput.Configuration 276 // TODO error checking / handling on the Set() calls. 277 d.Set("arn", function.FunctionArn) 278 d.Set("description", function.Description) 279 d.Set("handler", function.Handler) 280 d.Set("memory_size", function.MemorySize) 281 d.Set("last_modified", function.LastModified) 282 d.Set("role", function.Role) 283 d.Set("runtime", function.Runtime) 284 d.Set("timeout", function.Timeout) 285 if config := flattenLambdaVpcConfigResponse(function.VpcConfig); len(config) > 0 { 286 log.Printf("[INFO] Setting Lambda %s VPC config %#v from API", d.Id(), config) 287 err := d.Set("vpc_config", config) 288 if err != nil { 289 return fmt.Errorf("Failed setting vpc_config: %s", err) 290 } 291 } 292 d.Set("source_code_hash", function.CodeSha256) 293 294 // List is sorted from oldest to latest 295 // so this may get costly over time :'( 296 var lastVersion, lastQualifiedArn string 297 err = listVersionsByFunctionPages(conn, &lambda.ListVersionsByFunctionInput{ 298 FunctionName: function.FunctionName, 299 MaxItems: aws.Int64(10000), 300 }, func(p *lambda.ListVersionsByFunctionOutput, lastPage bool) bool { 301 if lastPage { 302 last := p.Versions[len(p.Versions)-1] 303 lastVersion = *last.Version 304 lastQualifiedArn = *last.FunctionArn 305 return true 306 } 307 return false 308 }) 309 if err != nil { 310 return err 311 } 312 313 d.Set("version", lastVersion) 314 d.Set("qualified_arn", lastQualifiedArn) 315 316 return nil 317 } 318 319 func listVersionsByFunctionPages(c *lambda.Lambda, input *lambda.ListVersionsByFunctionInput, 320 fn func(p *lambda.ListVersionsByFunctionOutput, lastPage bool) bool) error { 321 for { 322 page, err := c.ListVersionsByFunction(input) 323 if err != nil { 324 return err 325 } 326 lastPage := page.NextMarker == nil 327 328 shouldContinue := fn(page, lastPage) 329 if !shouldContinue || lastPage { 330 break 331 } 332 } 333 return nil 334 } 335 336 // resourceAwsLambdaFunction maps to: 337 // DeleteFunction in the API / SDK 338 func resourceAwsLambdaFunctionDelete(d *schema.ResourceData, meta interface{}) error { 339 conn := meta.(*AWSClient).lambdaconn 340 341 log.Printf("[INFO] Deleting Lambda Function: %s", d.Id()) 342 343 params := &lambda.DeleteFunctionInput{ 344 FunctionName: aws.String(d.Get("function_name").(string)), 345 } 346 347 _, err := conn.DeleteFunction(params) 348 if err != nil { 349 return fmt.Errorf("Error deleting Lambda Function: %s", err) 350 } 351 352 d.SetId("") 353 354 return nil 355 } 356 357 // resourceAwsLambdaFunctionUpdate maps to: 358 // UpdateFunctionCode in the API / SDK 359 func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) error { 360 conn := meta.(*AWSClient).lambdaconn 361 362 d.Partial(true) 363 364 if d.HasChange("filename") || d.HasChange("source_code_hash") || d.HasChange("s3_bucket") || d.HasChange("s3_key") || d.HasChange("s3_object_version") { 365 codeReq := &lambda.UpdateFunctionCodeInput{ 366 FunctionName: aws.String(d.Id()), 367 Publish: aws.Bool(d.Get("publish").(bool)), 368 } 369 370 if v, ok := d.GetOk("filename"); ok { 371 // Grab an exclusive lock so that we're only reading one function into 372 // memory at a time. 373 // See https://github.com/hashicorp/terraform/issues/9364 374 awsMutexKV.Lock(awsMutexLambdaKey) 375 defer awsMutexKV.Unlock(awsMutexLambdaKey) 376 file, err := loadFileContent(v.(string)) 377 if err != nil { 378 return fmt.Errorf("Unable to load %q: %s", v.(string), err) 379 } 380 codeReq.ZipFile = file 381 } else { 382 s3Bucket, _ := d.GetOk("s3_bucket") 383 s3Key, _ := d.GetOk("s3_key") 384 s3ObjectVersion, versionOk := d.GetOk("s3_object_version") 385 386 codeReq.S3Bucket = aws.String(s3Bucket.(string)) 387 codeReq.S3Key = aws.String(s3Key.(string)) 388 if versionOk { 389 codeReq.S3ObjectVersion = aws.String(s3ObjectVersion.(string)) 390 } 391 } 392 393 log.Printf("[DEBUG] Send Update Lambda Function Code request: %#v", codeReq) 394 395 _, err := conn.UpdateFunctionCode(codeReq) 396 if err != nil { 397 return fmt.Errorf("Error modifying Lambda Function Code %s: %s", d.Id(), err) 398 } 399 400 d.SetPartial("filename") 401 d.SetPartial("source_code_hash") 402 d.SetPartial("s3_bucket") 403 d.SetPartial("s3_key") 404 d.SetPartial("s3_object_version") 405 } 406 407 configReq := &lambda.UpdateFunctionConfigurationInput{ 408 FunctionName: aws.String(d.Id()), 409 } 410 411 configUpdate := false 412 if d.HasChange("description") { 413 configReq.Description = aws.String(d.Get("description").(string)) 414 configUpdate = true 415 } 416 if d.HasChange("handler") { 417 configReq.Handler = aws.String(d.Get("handler").(string)) 418 configUpdate = true 419 } 420 if d.HasChange("memory_size") { 421 configReq.MemorySize = aws.Int64(int64(d.Get("memory_size").(int))) 422 configUpdate = true 423 } 424 if d.HasChange("role") { 425 configReq.Role = aws.String(d.Get("role").(string)) 426 configUpdate = true 427 } 428 if d.HasChange("timeout") { 429 configReq.Timeout = aws.Int64(int64(d.Get("timeout").(int))) 430 configUpdate = true 431 } 432 433 if configUpdate { 434 log.Printf("[DEBUG] Send Update Lambda Function Configuration request: %#v", configReq) 435 _, err := conn.UpdateFunctionConfiguration(configReq) 436 if err != nil { 437 return fmt.Errorf("Error modifying Lambda Function Configuration %s: %s", d.Id(), err) 438 } 439 d.SetPartial("description") 440 d.SetPartial("handler") 441 d.SetPartial("memory_size") 442 d.SetPartial("role") 443 d.SetPartial("timeout") 444 } 445 d.Partial(false) 446 447 return resourceAwsLambdaFunctionRead(d, meta) 448 } 449 450 // loadFileContent returns contents of a file in a given path 451 func loadFileContent(v string) ([]byte, error) { 452 filename, err := homedir.Expand(v) 453 if err != nil { 454 return nil, err 455 } 456 fileContent, err := ioutil.ReadFile(filename) 457 if err != nil { 458 return nil, err 459 } 460 return fileContent, nil 461 } 462 463 func validateVPCConfig(v interface{}) (map[string]interface{}, error) { 464 configs := v.([]interface{}) 465 if len(configs) > 1 { 466 return nil, errors.New("Only a single vpc_config block is expected") 467 } 468 469 config, ok := configs[0].(map[string]interface{}) 470 471 if !ok { 472 return nil, errors.New("vpc_config is <nil>") 473 } 474 475 // if subnet_ids and security_group_ids are both empty then the VPC is optional 476 if config["subnet_ids"].(*schema.Set).Len() == 0 && config["security_group_ids"].(*schema.Set).Len() == 0 { 477 return nil, nil 478 } 479 480 if config["subnet_ids"].(*schema.Set).Len() == 0 { 481 return nil, errors.New("vpc_config.subnet_ids cannot be empty") 482 } 483 484 if config["security_group_ids"].(*schema.Set).Len() == 0 { 485 return nil, errors.New("vpc_config.security_group_ids cannot be empty") 486 } 487 488 return config, nil 489 }