github.com/boyvanduuren/terraform@v0.7.0-rc2.0.20160805175930-de822d909c40/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 var subnetIds []*string 185 for _, id := range config["subnet_ids"].(*schema.Set).List() { 186 subnetIds = append(subnetIds, aws.String(id.(string))) 187 } 188 189 var securityGroupIds []*string 190 for _, id := range config["security_group_ids"].(*schema.Set).List() { 191 securityGroupIds = append(securityGroupIds, aws.String(id.(string))) 192 } 193 194 params.VpcConfig = &lambda.VpcConfig{ 195 SubnetIds: subnetIds, 196 SecurityGroupIds: securityGroupIds, 197 } 198 } 199 200 // IAM profiles can take ~10 seconds to propagate in AWS: 201 // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console 202 // Error creating Lambda function: InvalidParameterValueException: The role defined for the task cannot be assumed by Lambda. 203 err := resource.Retry(1*time.Minute, func() *resource.RetryError { 204 _, err := conn.CreateFunction(params) 205 if err != nil { 206 log.Printf("[ERROR] Received %q, retrying CreateFunction", err) 207 if awserr, ok := err.(awserr.Error); ok { 208 if awserr.Code() == "InvalidParameterValueException" { 209 log.Printf("[DEBUG] InvalidParameterValueException creating Lambda Function: %s", awserr) 210 return resource.RetryableError(awserr) 211 } 212 } 213 log.Printf("[DEBUG] Error creating Lambda Function: %s", err) 214 return resource.NonRetryableError(err) 215 } 216 return nil 217 }) 218 if err != nil { 219 return fmt.Errorf("Error creating Lambda function: %s", err) 220 } 221 222 d.SetId(d.Get("function_name").(string)) 223 224 return resourceAwsLambdaFunctionRead(d, meta) 225 } 226 227 // resourceAwsLambdaFunctionRead maps to: 228 // GetFunction in the API / SDK 229 func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) error { 230 conn := meta.(*AWSClient).lambdaconn 231 232 log.Printf("[DEBUG] Fetching Lambda Function: %s", d.Id()) 233 234 params := &lambda.GetFunctionInput{ 235 FunctionName: aws.String(d.Get("function_name").(string)), 236 } 237 238 getFunctionOutput, err := conn.GetFunction(params) 239 if err != nil { 240 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" && !d.IsNewResource() { 241 d.SetId("") 242 return nil 243 } 244 return err 245 } 246 247 // getFunctionOutput.Code.Location is a pre-signed URL pointing at the zip 248 // file that we uploaded when we created the resource. You can use it to 249 // download the code from AWS. The other part is 250 // getFunctionOutput.Configuration which holds metadata. 251 252 function := getFunctionOutput.Configuration 253 // TODO error checking / handling on the Set() calls. 254 d.Set("arn", function.FunctionArn) 255 d.Set("description", function.Description) 256 d.Set("handler", function.Handler) 257 d.Set("memory_size", function.MemorySize) 258 d.Set("last_modified", function.LastModified) 259 d.Set("role", function.Role) 260 d.Set("runtime", function.Runtime) 261 d.Set("timeout", function.Timeout) 262 if config := flattenLambdaVpcConfigResponse(function.VpcConfig); len(config) > 0 { 263 log.Printf("[INFO] Setting Lambda %s VPC config %#v from API", d.Id(), config) 264 err := d.Set("vpc_config", config) 265 if err != nil { 266 return fmt.Errorf("Failed setting vpc_config: %s", err) 267 } 268 } 269 d.Set("source_code_hash", function.CodeSha256) 270 271 return nil 272 } 273 274 // resourceAwsLambdaFunction maps to: 275 // DeleteFunction in the API / SDK 276 func resourceAwsLambdaFunctionDelete(d *schema.ResourceData, meta interface{}) error { 277 conn := meta.(*AWSClient).lambdaconn 278 279 log.Printf("[INFO] Deleting Lambda Function: %s", d.Id()) 280 281 params := &lambda.DeleteFunctionInput{ 282 FunctionName: aws.String(d.Get("function_name").(string)), 283 } 284 285 _, err := conn.DeleteFunction(params) 286 if err != nil { 287 return fmt.Errorf("Error deleting Lambda Function: %s", err) 288 } 289 290 d.SetId("") 291 292 return nil 293 } 294 295 // resourceAwsLambdaFunctionUpdate maps to: 296 // UpdateFunctionCode in the API / SDK 297 func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) error { 298 conn := meta.(*AWSClient).lambdaconn 299 300 d.Partial(true) 301 302 codeReq := &lambda.UpdateFunctionCodeInput{ 303 FunctionName: aws.String(d.Id()), 304 } 305 306 codeUpdate := false 307 if d.HasChange("filename") || d.HasChange("source_code_hash") { 308 name := d.Get("filename").(string) 309 file, err := loadFileContent(name) 310 if err != nil { 311 return fmt.Errorf("Unable to load %q: %s", name, err) 312 } 313 codeReq.ZipFile = file 314 codeUpdate = true 315 } 316 if d.HasChange("s3_bucket") || d.HasChange("s3_key") || d.HasChange("s3_object_version") { 317 codeReq.S3Bucket = aws.String(d.Get("s3_bucket").(string)) 318 codeReq.S3Key = aws.String(d.Get("s3_key").(string)) 319 codeReq.S3ObjectVersion = aws.String(d.Get("s3_object_version").(string)) 320 codeUpdate = true 321 } 322 323 if codeUpdate { 324 log.Printf("[DEBUG] Send Update Lambda Function Code request: %#v", codeReq) 325 _, err := conn.UpdateFunctionCode(codeReq) 326 if err != nil { 327 return fmt.Errorf("Error modifying Lambda Function Code %s: %s", d.Id(), err) 328 } 329 330 d.SetPartial("filename") 331 d.SetPartial("source_code_hash") 332 d.SetPartial("s3_bucket") 333 d.SetPartial("s3_key") 334 d.SetPartial("s3_object_version") 335 } 336 337 configReq := &lambda.UpdateFunctionConfigurationInput{ 338 FunctionName: aws.String(d.Id()), 339 } 340 341 configUpdate := false 342 if d.HasChange("description") { 343 configReq.Description = aws.String(d.Get("description").(string)) 344 configUpdate = true 345 } 346 if d.HasChange("handler") { 347 configReq.Handler = aws.String(d.Get("handler").(string)) 348 configUpdate = true 349 } 350 if d.HasChange("memory_size") { 351 configReq.MemorySize = aws.Int64(int64(d.Get("memory_size").(int))) 352 configUpdate = true 353 } 354 if d.HasChange("role") { 355 configReq.Role = aws.String(d.Get("role").(string)) 356 configUpdate = true 357 } 358 if d.HasChange("timeout") { 359 configReq.Timeout = aws.Int64(int64(d.Get("timeout").(int))) 360 configUpdate = true 361 } 362 363 if configUpdate { 364 log.Printf("[DEBUG] Send Update Lambda Function Configuration request: %#v", configReq) 365 _, err := conn.UpdateFunctionConfiguration(configReq) 366 if err != nil { 367 return fmt.Errorf("Error modifying Lambda Function Configuration %s: %s", d.Id(), err) 368 } 369 d.SetPartial("description") 370 d.SetPartial("handler") 371 d.SetPartial("memory_size") 372 d.SetPartial("role") 373 d.SetPartial("timeout") 374 } 375 d.Partial(false) 376 377 return resourceAwsLambdaFunctionRead(d, meta) 378 } 379 380 // loadFileContent returns contents of a file in a given path 381 func loadFileContent(v string) ([]byte, error) { 382 filename, err := homedir.Expand(v) 383 if err != nil { 384 return nil, err 385 } 386 fileContent, err := ioutil.ReadFile(filename) 387 if err != nil { 388 return nil, err 389 } 390 return fileContent, nil 391 } 392 393 func validateVPCConfig(v interface{}) (map[string]interface{}, error) { 394 configs := v.([]interface{}) 395 if len(configs) > 1 { 396 return nil, errors.New("Only a single vpc_config block is expected") 397 } 398 399 config, ok := configs[0].(map[string]interface{}) 400 401 if !ok { 402 return nil, errors.New("vpc_config is <nil>") 403 } 404 405 if config["subnet_ids"].(*schema.Set).Len() == 0 { 406 return nil, errors.New("vpc_config.subnet_ids cannot be empty") 407 } 408 409 if config["security_group_ids"].(*schema.Set).Len() == 0 { 410 return nil, errors.New("vpc_config.security_group_ids cannot be empty") 411 } 412 413 return config, nil 414 }