github.com/pmcatominey/terraform@v0.7.0-rc2.0.20160708105029-1401a52a5cc5/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:aws: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": &schema.Schema{ 28 Type: schema.TypeString, 29 Required: true, 30 ForceNew: true, 31 ValidateFunc: validateLambdaPermissionAction, 32 }, 33 "function_name": &schema.Schema{ 34 Type: schema.TypeString, 35 Required: true, 36 ForceNew: true, 37 ValidateFunc: validateLambdaFunctionName, 38 }, 39 "principal": &schema.Schema{ 40 Type: schema.TypeString, 41 Required: true, 42 ForceNew: true, 43 }, 44 "qualifier": &schema.Schema{ 45 Type: schema.TypeString, 46 Optional: true, 47 ForceNew: true, 48 ValidateFunc: validateLambdaQualifier, 49 }, 50 "source_account": &schema.Schema{ 51 Type: schema.TypeString, 52 Optional: true, 53 ForceNew: true, 54 ValidateFunc: validateAwsAccountId, 55 }, 56 "source_arn": &schema.Schema{ 57 Type: schema.TypeString, 58 Optional: true, 59 ForceNew: true, 60 ValidateFunc: validateArn, 61 }, 62 "statement_id": &schema.Schema{ 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 occured 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 d.Set("qualifier", qualifier) 216 } 217 218 // Save Lambda function name in the same format 219 if strings.HasPrefix(d.Get("function_name").(string), "arn:aws:lambda:") { 220 // Strip qualifier off 221 trimmedArn := strings.TrimSuffix(statement.Resource, ":"+qualifier) 222 d.Set("function_name", trimmedArn) 223 } else { 224 functionName, err := getFunctionNameFromLambdaArn(statement.Resource) 225 if err != nil { 226 return err 227 } 228 d.Set("function_name", functionName) 229 } 230 231 d.Set("action", statement.Action) 232 d.Set("principal", statement.Principal["Service"]) 233 234 if stringEquals, ok := statement.Condition["StringEquals"]; ok { 235 d.Set("source_account", stringEquals["AWS:SourceAccount"]) 236 } 237 238 if arnLike, ok := statement.Condition["ArnLike"]; ok { 239 d.Set("source_arn", arnLike["AWS:SourceArn"]) 240 } 241 242 return nil 243 } 244 245 func resourceAwsLambdaPermissionDelete(d *schema.ResourceData, meta interface{}) error { 246 conn := meta.(*AWSClient).lambdaconn 247 248 functionName := d.Get("function_name").(string) 249 250 // There is a bug in the API (reported and acknowledged by AWS) 251 // which causes some permissions to be ignored when API calls are sent in parallel 252 // We work around this bug via mutex 253 awsMutexKV.Lock(functionName) 254 defer awsMutexKV.Unlock(functionName) 255 256 input := lambda.RemovePermissionInput{ 257 FunctionName: aws.String(functionName), 258 StatementId: aws.String(d.Id()), 259 } 260 261 if v, ok := d.GetOk("qualifier"); ok { 262 input.Qualifier = aws.String(v.(string)) 263 } 264 265 log.Printf("[DEBUG] Removing Lambda permission: %s", input) 266 _, err := conn.RemovePermission(&input) 267 if err != nil { 268 return err 269 } 270 271 err = resource.Retry(5*time.Minute, func() *resource.RetryError { 272 log.Printf("[DEBUG] Checking if Lambda permission %q is deleted", d.Id()) 273 274 params := &lambda.GetPolicyInput{ 275 FunctionName: aws.String(d.Get("function_name").(string)), 276 } 277 if v, ok := d.GetOk("qualifier"); ok { 278 params.Qualifier = aws.String(v.(string)) 279 } 280 281 log.Printf("[DEBUG] Looking for Lambda permission: %s", *params) 282 resp, err := conn.GetPolicy(params) 283 if err != nil { 284 if awsErr, ok := err.(awserr.Error); ok { 285 if awsErr.Code() == "ResourceNotFoundException" { 286 return nil 287 } 288 } 289 return resource.NonRetryableError(err) 290 } 291 292 if resp.Policy == nil { 293 return nil 294 } 295 296 policyInBytes := []byte(*resp.Policy) 297 policy := LambdaPolicy{} 298 err = json.Unmarshal(policyInBytes, &policy) 299 if err != nil { 300 return resource.RetryableError( 301 fmt.Errorf("Error unmarshalling Lambda policy: %s", err)) 302 } 303 304 _, err = findLambdaPolicyStatementById(&policy, d.Id()) 305 if err != nil { 306 return nil 307 } 308 309 log.Printf("[DEBUG] No error when checking if Lambda permission %s is deleted", d.Id()) 310 return nil 311 }) 312 313 if err != nil { 314 return fmt.Errorf("Failed removing Lambda permission: %s", err) 315 } 316 317 log.Printf("[DEBUG] Lambda permission with ID %q removed", d.Id()) 318 d.SetId("") 319 320 return nil 321 } 322 323 func findLambdaPolicyStatementById(policy *LambdaPolicy, id string) ( 324 *LambdaPolicyStatement, error) { 325 326 log.Printf("[DEBUG] Received %d statements in Lambda policy: %s", len(policy.Statement), policy.Statement) 327 for _, statement := range policy.Statement { 328 if statement.Sid == id { 329 return &statement, nil 330 } 331 } 332 333 return nil, &resource.NotFoundError{ 334 LastRequest: id, 335 LastResponse: policy, 336 Message: fmt.Sprintf("Failed to find statement %q in Lambda policy:\n%s", id, policy.Statement), 337 } 338 } 339 340 func getQualifierFromLambdaAliasOrVersionArn(arn string) (string, error) { 341 matches := regexp.MustCompile(LambdaFunctionRegexp).FindStringSubmatch(arn) 342 if len(matches) < 8 || matches[7] == "" { 343 return "", fmt.Errorf("Invalid ARN or otherwise unable to get qualifier from ARN (%q)", 344 arn) 345 } 346 347 return matches[7], nil 348 } 349 350 func getFunctionNameFromLambdaArn(arn string) (string, error) { 351 matches := regexp.MustCompile(LambdaFunctionRegexp).FindStringSubmatch(arn) 352 if len(matches) < 6 || matches[5] == "" { 353 return "", fmt.Errorf("Invalid ARN or otherwise unable to get qualifier from ARN (%q)", 354 arn) 355 } 356 return matches[5], nil 357 } 358 359 type LambdaPolicy struct { 360 Version string 361 Statement []LambdaPolicyStatement 362 Id string 363 } 364 365 type LambdaPolicyStatement struct { 366 Condition map[string]map[string]string 367 Action string 368 Resource string 369 Effect string 370 Principal map[string]string 371 Sid string 372 }