github.com/daveadams/terraform@v0.6.4-0.20160830094355-13ce74975936/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 "vpc_config": &schema.Schema{ 89 Type: schema.TypeList, 90 Optional: true, 91 ForceNew: true, 92 Elem: &schema.Resource{ 93 Schema: map[string]*schema.Schema{ 94 "subnet_ids": &schema.Schema{ 95 Type: schema.TypeSet, 96 Required: true, 97 ForceNew: true, 98 Elem: &schema.Schema{Type: schema.TypeString}, 99 Set: schema.HashString, 100 }, 101 "security_group_ids": &schema.Schema{ 102 Type: schema.TypeSet, 103 Required: true, 104 ForceNew: true, 105 Elem: &schema.Schema{Type: schema.TypeString}, 106 Set: schema.HashString, 107 }, 108 "vpc_id": &schema.Schema{ 109 Type: schema.TypeString, 110 Computed: true, 111 }, 112 }, 113 }, 114 }, 115 "arn": &schema.Schema{ 116 Type: schema.TypeString, 117 Computed: true, 118 }, 119 "last_modified": &schema.Schema{ 120 Type: schema.TypeString, 121 Computed: true, 122 }, 123 "source_code_hash": &schema.Schema{ 124 Type: schema.TypeString, 125 Optional: true, 126 Computed: true, 127 }, 128 }, 129 } 130 } 131 132 // resourceAwsLambdaFunction maps to: 133 // CreateFunction in the API / SDK 134 func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) error { 135 conn := meta.(*AWSClient).lambdaconn 136 137 functionName := d.Get("function_name").(string) 138 iamRole := d.Get("role").(string) 139 140 log.Printf("[DEBUG] Creating Lambda Function %s with role %s", functionName, iamRole) 141 142 var functionCode *lambda.FunctionCode 143 if v, ok := d.GetOk("filename"); ok { 144 file, err := loadFileContent(v.(string)) 145 if err != nil { 146 return fmt.Errorf("Unable to load %q: %s", v.(string), err) 147 } 148 functionCode = &lambda.FunctionCode{ 149 ZipFile: file, 150 } 151 } else { 152 s3Bucket, bucketOk := d.GetOk("s3_bucket") 153 s3Key, keyOk := d.GetOk("s3_key") 154 s3ObjectVersion, versionOk := d.GetOk("s3_object_version") 155 if !bucketOk || !keyOk { 156 return errors.New("s3_bucket and s3_key must all be set while using S3 code source") 157 } 158 functionCode = &lambda.FunctionCode{ 159 S3Bucket: aws.String(s3Bucket.(string)), 160 S3Key: aws.String(s3Key.(string)), 161 } 162 if versionOk { 163 functionCode.S3ObjectVersion = aws.String(s3ObjectVersion.(string)) 164 } 165 } 166 167 params := &lambda.CreateFunctionInput{ 168 Code: functionCode, 169 Description: aws.String(d.Get("description").(string)), 170 FunctionName: aws.String(functionName), 171 Handler: aws.String(d.Get("handler").(string)), 172 MemorySize: aws.Int64(int64(d.Get("memory_size").(int))), 173 Role: aws.String(iamRole), 174 Runtime: aws.String(d.Get("runtime").(string)), 175 Timeout: aws.Int64(int64(d.Get("timeout").(int))), 176 } 177 178 if v, ok := d.GetOk("vpc_config"); ok { 179 config, err := validateVPCConfig(v) 180 if err != nil { 181 return err 182 } 183 184 if config != nil { 185 var subnetIds []*string 186 for _, id := range config["subnet_ids"].(*schema.Set).List() { 187 subnetIds = append(subnetIds, aws.String(id.(string))) 188 } 189 190 var securityGroupIds []*string 191 for _, id := range config["security_group_ids"].(*schema.Set).List() { 192 securityGroupIds = append(securityGroupIds, aws.String(id.(string))) 193 } 194 195 params.VpcConfig = &lambda.VpcConfig{ 196 SubnetIds: subnetIds, 197 SecurityGroupIds: securityGroupIds, 198 } 199 } 200 } 201 202 // IAM profiles can take ~10 seconds to propagate in AWS: 203 // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console 204 // Error creating Lambda function: InvalidParameterValueException: The role defined for the task cannot be assumed by Lambda. 205 err := resource.Retry(1*time.Minute, func() *resource.RetryError { 206 _, err := conn.CreateFunction(params) 207 if err != nil { 208 log.Printf("[ERROR] Received %q, retrying CreateFunction", err) 209 if awserr, ok := err.(awserr.Error); ok { 210 if awserr.Code() == "InvalidParameterValueException" { 211 log.Printf("[DEBUG] InvalidParameterValueException creating Lambda Function: %s", awserr) 212 return resource.RetryableError(awserr) 213 } 214 } 215 log.Printf("[DEBUG] Error creating Lambda Function: %s", err) 216 return resource.NonRetryableError(err) 217 } 218 return nil 219 }) 220 if err != nil { 221 return fmt.Errorf("Error creating Lambda function: %s", err) 222 } 223 224 d.SetId(d.Get("function_name").(string)) 225 226 return resourceAwsLambdaFunctionRead(d, meta) 227 } 228 229 // resourceAwsLambdaFunctionRead maps to: 230 // GetFunction in the API / SDK 231 func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) error { 232 conn := meta.(*AWSClient).lambdaconn 233 234 log.Printf("[DEBUG] Fetching Lambda Function: %s", d.Id()) 235 236 params := &lambda.GetFunctionInput{ 237 FunctionName: aws.String(d.Get("function_name").(string)), 238 } 239 240 getFunctionOutput, err := conn.GetFunction(params) 241 if err != nil { 242 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" && !d.IsNewResource() { 243 d.SetId("") 244 return nil 245 } 246 return err 247 } 248 249 // getFunctionOutput.Code.Location is a pre-signed URL pointing at the zip 250 // file that we uploaded when we created the resource. You can use it to 251 // download the code from AWS. The other part is 252 // getFunctionOutput.Configuration which holds metadata. 253 254 function := getFunctionOutput.Configuration 255 // TODO error checking / handling on the Set() calls. 256 d.Set("arn", function.FunctionArn) 257 d.Set("description", function.Description) 258 d.Set("handler", function.Handler) 259 d.Set("memory_size", function.MemorySize) 260 d.Set("last_modified", function.LastModified) 261 d.Set("role", function.Role) 262 d.Set("runtime", function.Runtime) 263 d.Set("timeout", function.Timeout) 264 if config := flattenLambdaVpcConfigResponse(function.VpcConfig); len(config) > 0 { 265 log.Printf("[INFO] Setting Lambda %s VPC config %#v from API", d.Id(), config) 266 err := d.Set("vpc_config", config) 267 if err != nil { 268 return fmt.Errorf("Failed setting vpc_config: %s", err) 269 } 270 } 271 d.Set("source_code_hash", function.CodeSha256) 272 273 return nil 274 } 275 276 // resourceAwsLambdaFunction maps to: 277 // DeleteFunction in the API / SDK 278 func resourceAwsLambdaFunctionDelete(d *schema.ResourceData, meta interface{}) error { 279 conn := meta.(*AWSClient).lambdaconn 280 281 log.Printf("[INFO] Deleting Lambda Function: %s", d.Id()) 282 283 params := &lambda.DeleteFunctionInput{ 284 FunctionName: aws.String(d.Get("function_name").(string)), 285 } 286 287 _, err := conn.DeleteFunction(params) 288 if err != nil { 289 return fmt.Errorf("Error deleting Lambda Function: %s", err) 290 } 291 292 d.SetId("") 293 294 return nil 295 } 296 297 // resourceAwsLambdaFunctionUpdate maps to: 298 // UpdateFunctionCode in the API / SDK 299 func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) error { 300 conn := meta.(*AWSClient).lambdaconn 301 302 d.Partial(true) 303 304 if d.HasChange("filename") || d.HasChange("source_code_hash") || d.HasChange("s3_bucket") || d.HasChange("s3_key") || d.HasChange("s3_object_version") { 305 codeReq := &lambda.UpdateFunctionCodeInput{ 306 FunctionName: aws.String(d.Id()), 307 } 308 309 if v, ok := d.GetOk("filename"); ok { 310 file, err := loadFileContent(v.(string)) 311 if err != nil { 312 return fmt.Errorf("Unable to load %q: %s", v.(string), err) 313 } 314 codeReq.ZipFile = file 315 } else { 316 s3Bucket, _ := d.GetOk("s3_bucket") 317 s3Key, _ := d.GetOk("s3_key") 318 s3ObjectVersion, versionOk := d.GetOk("s3_object_version") 319 320 codeReq.S3Bucket = aws.String(s3Bucket.(string)) 321 codeReq.S3Key = aws.String(s3Key.(string)) 322 if versionOk { 323 codeReq.S3ObjectVersion = aws.String(s3ObjectVersion.(string)) 324 } 325 } 326 327 log.Printf("[DEBUG] Send Update Lambda Function Code request: %#v", codeReq) 328 329 _, err := conn.UpdateFunctionCode(codeReq) 330 if err != nil { 331 return fmt.Errorf("Error modifying Lambda Function Code %s: %s", d.Id(), err) 332 } 333 334 d.SetPartial("filename") 335 d.SetPartial("source_code_hash") 336 d.SetPartial("s3_bucket") 337 d.SetPartial("s3_key") 338 d.SetPartial("s3_object_version") 339 } 340 341 configReq := &lambda.UpdateFunctionConfigurationInput{ 342 FunctionName: aws.String(d.Id()), 343 } 344 345 configUpdate := false 346 if d.HasChange("description") { 347 configReq.Description = aws.String(d.Get("description").(string)) 348 configUpdate = true 349 } 350 if d.HasChange("handler") { 351 configReq.Handler = aws.String(d.Get("handler").(string)) 352 configUpdate = true 353 } 354 if d.HasChange("memory_size") { 355 configReq.MemorySize = aws.Int64(int64(d.Get("memory_size").(int))) 356 configUpdate = true 357 } 358 if d.HasChange("role") { 359 configReq.Role = aws.String(d.Get("role").(string)) 360 configUpdate = true 361 } 362 if d.HasChange("timeout") { 363 configReq.Timeout = aws.Int64(int64(d.Get("timeout").(int))) 364 configUpdate = true 365 } 366 367 if configUpdate { 368 log.Printf("[DEBUG] Send Update Lambda Function Configuration request: %#v", configReq) 369 _, err := conn.UpdateFunctionConfiguration(configReq) 370 if err != nil { 371 return fmt.Errorf("Error modifying Lambda Function Configuration %s: %s", d.Id(), err) 372 } 373 d.SetPartial("description") 374 d.SetPartial("handler") 375 d.SetPartial("memory_size") 376 d.SetPartial("role") 377 d.SetPartial("timeout") 378 } 379 d.Partial(false) 380 381 return resourceAwsLambdaFunctionRead(d, meta) 382 } 383 384 // loadFileContent returns contents of a file in a given path 385 func loadFileContent(v string) ([]byte, error) { 386 filename, err := homedir.Expand(v) 387 if err != nil { 388 return nil, err 389 } 390 fileContent, err := ioutil.ReadFile(filename) 391 if err != nil { 392 return nil, err 393 } 394 return fileContent, nil 395 } 396 397 func validateVPCConfig(v interface{}) (map[string]interface{}, error) { 398 configs := v.([]interface{}) 399 if len(configs) > 1 { 400 return nil, errors.New("Only a single vpc_config block is expected") 401 } 402 403 config, ok := configs[0].(map[string]interface{}) 404 405 if !ok { 406 return nil, errors.New("vpc_config is <nil>") 407 } 408 409 // if subnet_ids and security_group_ids are both empty then the VPC is optional 410 if config["subnet_ids"].(*schema.Set).Len() == 0 && config["security_group_ids"].(*schema.Set).Len() == 0 { 411 return nil, nil 412 } 413 414 if config["subnet_ids"].(*schema.Set).Len() == 0 { 415 return nil, errors.New("vpc_config.subnet_ids cannot be empty") 416 } 417 418 if config["security_group_ids"].(*schema.Set).Len() == 0 { 419 return nil, errors.New("vpc_config.security_group_ids cannot be empty") 420 } 421 422 return config, nil 423 }