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