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