github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/aws/resource_aws_lambda_permission.go (about) 1 package aws 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "regexp" 8 "strings" 9 "time" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/aws/awserr" 13 "github.com/aws/aws-sdk-go/service/lambda" 14 "github.com/hashicorp/terraform/helper/resource" 15 "github.com/hashicorp/terraform/helper/schema" 16 ) 17 18 var LambdaFunctionRegexp = `^(arn:[\w-]+:lambda:)?([a-z]{2}-[a-z]+-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\$LATEST|[a-zA-Z0-9-_]+))?$` 19 20 func resourceAwsLambdaPermission() *schema.Resource { 21 return &schema.Resource{ 22 Create: resourceAwsLambdaPermissionCreate, 23 Read: resourceAwsLambdaPermissionRead, 24 Delete: resourceAwsLambdaPermissionDelete, 25 26 Schema: map[string]*schema.Schema{ 27 "action": { 28 Type: schema.TypeString, 29 Required: true, 30 ForceNew: true, 31 ValidateFunc: validateLambdaPermissionAction, 32 }, 33 "function_name": { 34 Type: schema.TypeString, 35 Required: true, 36 ForceNew: true, 37 ValidateFunc: validateLambdaFunctionName, 38 }, 39 "principal": { 40 Type: schema.TypeString, 41 Required: true, 42 ForceNew: true, 43 }, 44 "qualifier": { 45 Type: schema.TypeString, 46 Optional: true, 47 ForceNew: true, 48 ValidateFunc: validateLambdaQualifier, 49 }, 50 "source_account": { 51 Type: schema.TypeString, 52 Optional: true, 53 ForceNew: true, 54 ValidateFunc: validateAwsAccountId, 55 }, 56 "source_arn": { 57 Type: schema.TypeString, 58 Optional: true, 59 ForceNew: true, 60 ValidateFunc: validateArn, 61 }, 62 "statement_id": { 63 Type: schema.TypeString, 64 Required: true, 65 ForceNew: true, 66 ValidateFunc: validatePolicyStatementId, 67 }, 68 }, 69 } 70 } 71 72 func resourceAwsLambdaPermissionCreate(d *schema.ResourceData, meta interface{}) error { 73 conn := meta.(*AWSClient).lambdaconn 74 75 functionName := d.Get("function_name").(string) 76 77 // There is a bug in the API (reported and acknowledged by AWS) 78 // which causes some permissions to be ignored when API calls are sent in parallel 79 // We work around this bug via mutex 80 awsMutexKV.Lock(functionName) 81 defer awsMutexKV.Unlock(functionName) 82 83 input := lambda.AddPermissionInput{ 84 Action: aws.String(d.Get("action").(string)), 85 FunctionName: aws.String(functionName), 86 Principal: aws.String(d.Get("principal").(string)), 87 StatementId: aws.String(d.Get("statement_id").(string)), 88 } 89 90 if v, ok := d.GetOk("qualifier"); ok { 91 input.Qualifier = aws.String(v.(string)) 92 } 93 if v, ok := d.GetOk("source_account"); ok { 94 input.SourceAccount = aws.String(v.(string)) 95 } 96 if v, ok := d.GetOk("source_arn"); ok { 97 input.SourceArn = aws.String(v.(string)) 98 } 99 100 log.Printf("[DEBUG] Adding new Lambda permission: %s", input) 101 var out *lambda.AddPermissionOutput 102 err := resource.Retry(1*time.Minute, func() *resource.RetryError { 103 var err error 104 out, err = conn.AddPermission(&input) 105 106 if err != nil { 107 if awsErr, ok := err.(awserr.Error); ok { 108 // IAM is eventually consistent :/ 109 if awsErr.Code() == "ResourceConflictException" { 110 return resource.RetryableError( 111 fmt.Errorf("[WARN] Error adding new Lambda Permission for %s, retrying: %s", 112 *input.FunctionName, err)) 113 } 114 } 115 return resource.NonRetryableError(err) 116 } 117 return nil 118 }) 119 120 if err != nil { 121 return err 122 } 123 124 if out != nil && out.Statement != nil { 125 log.Printf("[DEBUG] Created new Lambda permission: %s", *out.Statement) 126 } else { 127 log.Printf("[DEBUG] Created new Lambda permission, but no Statement was included") 128 } 129 130 d.SetId(d.Get("statement_id").(string)) 131 132 err = resource.Retry(5*time.Minute, func() *resource.RetryError { 133 // IAM is eventually cosistent :/ 134 err := resourceAwsLambdaPermissionRead(d, meta) 135 if err != nil { 136 if strings.HasPrefix(err.Error(), "Error reading Lambda policy: ResourceNotFoundException") { 137 return resource.RetryableError( 138 fmt.Errorf("[WARN] Error reading newly created Lambda Permission for %s, retrying: %s", 139 *input.FunctionName, err)) 140 } 141 if strings.HasPrefix(err.Error(), "Failed to find statement \""+d.Id()) { 142 return resource.RetryableError( 143 fmt.Errorf("[WARN] Error reading newly created Lambda Permission statement for %s, retrying: %s", 144 *input.FunctionName, err)) 145 } 146 147 log.Printf("[ERROR] An actual error occurred when expecting Lambda policy to be there: %s", err) 148 return resource.NonRetryableError(err) 149 } 150 return nil 151 }) 152 153 return err 154 } 155 156 func resourceAwsLambdaPermissionRead(d *schema.ResourceData, meta interface{}) error { 157 conn := meta.(*AWSClient).lambdaconn 158 159 input := lambda.GetPolicyInput{ 160 FunctionName: aws.String(d.Get("function_name").(string)), 161 } 162 if v, ok := d.GetOk("qualifier"); ok { 163 input.Qualifier = aws.String(v.(string)) 164 } 165 166 log.Printf("[DEBUG] Looking for Lambda permission: %s", input) 167 var out *lambda.GetPolicyOutput 168 var statement *LambdaPolicyStatement 169 err := resource.Retry(1*time.Minute, func() *resource.RetryError { 170 // IAM is eventually cosistent :/ 171 var err error 172 out, err = conn.GetPolicy(&input) 173 if err != nil { 174 if awsErr, ok := err.(awserr.Error); ok { 175 if awsErr.Code() == "ResourceNotFoundException" { 176 return resource.RetryableError(err) 177 } 178 } 179 return resource.NonRetryableError(err) 180 } 181 182 policyInBytes := []byte(*out.Policy) 183 policy := LambdaPolicy{} 184 err = json.Unmarshal(policyInBytes, &policy) 185 if err != nil { 186 return resource.NonRetryableError(err) 187 } 188 189 statement, err = findLambdaPolicyStatementById(&policy, d.Id()) 190 return resource.RetryableError(err) 191 }) 192 193 if err != nil { 194 // Missing whole policy or Lambda function (API error) 195 if awsErr, ok := err.(awserr.Error); ok { 196 if awsErr.Code() == "ResourceNotFoundException" { 197 log.Printf("[WARN] No Lambda Permission Policy found: %v", input) 198 d.SetId("") 199 return nil 200 } 201 } 202 203 // Missing permission inside valid policy 204 if nfErr, ok := err.(*resource.NotFoundError); ok { 205 log.Printf("[WARN] %s", nfErr) 206 d.SetId("") 207 return nil 208 } 209 210 return err 211 } 212 213 qualifier, err := getQualifierFromLambdaAliasOrVersionArn(statement.Resource) 214 if err != nil { 215 log.Printf("[ERR] Error getting Lambda Qualifier: %s", err) 216 } 217 d.Set("qualifier", qualifier) 218 219 // Save Lambda function name in the same format 220 if strings.HasPrefix(d.Get("function_name").(string), "arn:"+meta.(*AWSClient).partition+":lambda:") { 221 // Strip qualifier off 222 trimmedArn := strings.TrimSuffix(statement.Resource, ":"+qualifier) 223 d.Set("function_name", trimmedArn) 224 } else { 225 functionName, err := getFunctionNameFromLambdaArn(statement.Resource) 226 if err != nil { 227 return err 228 } 229 d.Set("function_name", functionName) 230 } 231 232 d.Set("action", statement.Action) 233 d.Set("principal", statement.Principal["Service"]) 234 235 if stringEquals, ok := statement.Condition["StringEquals"]; ok { 236 d.Set("source_account", stringEquals["AWS:SourceAccount"]) 237 } 238 239 if arnLike, ok := statement.Condition["ArnLike"]; ok { 240 d.Set("source_arn", arnLike["AWS:SourceArn"]) 241 } 242 243 return nil 244 } 245 246 func resourceAwsLambdaPermissionDelete(d *schema.ResourceData, meta interface{}) error { 247 conn := meta.(*AWSClient).lambdaconn 248 249 functionName := d.Get("function_name").(string) 250 251 // There is a bug in the API (reported and acknowledged by AWS) 252 // which causes some permissions to be ignored when API calls are sent in parallel 253 // We work around this bug via mutex 254 awsMutexKV.Lock(functionName) 255 defer awsMutexKV.Unlock(functionName) 256 257 input := lambda.RemovePermissionInput{ 258 FunctionName: aws.String(functionName), 259 StatementId: aws.String(d.Id()), 260 } 261 262 if v, ok := d.GetOk("qualifier"); ok { 263 input.Qualifier = aws.String(v.(string)) 264 } 265 266 log.Printf("[DEBUG] Removing Lambda permission: %s", input) 267 _, err := conn.RemovePermission(&input) 268 if err != nil { 269 return err 270 } 271 272 err = resource.Retry(5*time.Minute, func() *resource.RetryError { 273 log.Printf("[DEBUG] Checking if Lambda permission %q is deleted", d.Id()) 274 275 params := &lambda.GetPolicyInput{ 276 FunctionName: aws.String(d.Get("function_name").(string)), 277 } 278 if v, ok := d.GetOk("qualifier"); ok { 279 params.Qualifier = aws.String(v.(string)) 280 } 281 282 log.Printf("[DEBUG] Looking for Lambda permission: %s", *params) 283 resp, err := conn.GetPolicy(params) 284 if err != nil { 285 if awsErr, ok := err.(awserr.Error); ok { 286 if awsErr.Code() == "ResourceNotFoundException" { 287 return nil 288 } 289 } 290 return resource.NonRetryableError(err) 291 } 292 293 if resp.Policy == nil { 294 return nil 295 } 296 297 policyInBytes := []byte(*resp.Policy) 298 policy := LambdaPolicy{} 299 err = json.Unmarshal(policyInBytes, &policy) 300 if err != nil { 301 return resource.RetryableError( 302 fmt.Errorf("Error unmarshalling Lambda policy: %s", err)) 303 } 304 305 _, err = findLambdaPolicyStatementById(&policy, d.Id()) 306 if err != nil { 307 return nil 308 } 309 310 log.Printf("[DEBUG] No error when checking if Lambda permission %s is deleted", d.Id()) 311 return nil 312 }) 313 314 if err != nil { 315 return fmt.Errorf("Failed removing Lambda permission: %s", err) 316 } 317 318 log.Printf("[DEBUG] Lambda permission with ID %q removed", d.Id()) 319 d.SetId("") 320 321 return nil 322 } 323 324 func findLambdaPolicyStatementById(policy *LambdaPolicy, id string) ( 325 *LambdaPolicyStatement, error) { 326 327 log.Printf("[DEBUG] Received %d statements in Lambda policy: %s", len(policy.Statement), policy.Statement) 328 for _, statement := range policy.Statement { 329 if statement.Sid == id { 330 return &statement, nil 331 } 332 } 333 334 return nil, &resource.NotFoundError{ 335 LastRequest: id, 336 LastResponse: policy, 337 Message: fmt.Sprintf("Failed to find statement %q in Lambda policy:\n%s", id, policy.Statement), 338 } 339 } 340 341 func getQualifierFromLambdaAliasOrVersionArn(arn string) (string, error) { 342 matches := regexp.MustCompile(LambdaFunctionRegexp).FindStringSubmatch(arn) 343 if len(matches) < 8 || matches[7] == "" { 344 return "", fmt.Errorf("Invalid ARN or otherwise unable to get qualifier from ARN (%q)", 345 arn) 346 } 347 348 return matches[7], nil 349 } 350 351 func getFunctionNameFromLambdaArn(arn string) (string, error) { 352 matches := regexp.MustCompile(LambdaFunctionRegexp).FindStringSubmatch(arn) 353 if len(matches) < 6 || matches[5] == "" { 354 return "", fmt.Errorf("Invalid ARN or otherwise unable to get qualifier from ARN (%q)", 355 arn) 356 } 357 return matches[5], nil 358 } 359 360 type LambdaPolicy struct { 361 Version string 362 Statement []LambdaPolicyStatement 363 Id string 364 } 365 366 type LambdaPolicyStatement struct { 367 Condition map[string]map[string]string 368 Action string 369 Resource string 370 Effect string 371 Principal map[string]string 372 Sid string 373 }