github.com/joshgarnett/terraform@v0.5.4-0.20160219181435-92dc20bb3594/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() error { 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 fmt.Errorf("[WARN] Error adding new Lambda Permission for %s, retrying: %s", 111 *input.FunctionName, err) 112 } 113 } 114 return resource.RetryError{Err: err} 115 } 116 return nil 117 }) 118 119 if err != nil { 120 return err 121 } 122 123 log.Printf("[DEBUG] Created new Lambda permission: %s", *out.Statement) 124 125 d.SetId(d.Get("statement_id").(string)) 126 127 err = resource.Retry(5*time.Minute, func() error { 128 // IAM is eventually cosistent :/ 129 err := resourceAwsLambdaPermissionRead(d, meta) 130 if err != nil { 131 if strings.HasPrefix(err.Error(), "Error reading Lambda policy: ResourceNotFoundException") { 132 return fmt.Errorf("[WARN] Error reading newly created Lambda Permission for %s, retrying: %s", 133 *input.FunctionName, err) 134 } 135 if strings.HasPrefix(err.Error(), "Failed to find statement \""+d.Id()) { 136 return fmt.Errorf("[WARN] Error reading newly created Lambda Permission statement for %s, retrying: %s", 137 *input.FunctionName, err) 138 } 139 140 log.Printf("[ERROR] An actual error occured when expecting Lambda policy to be there: %s", err) 141 return resource.RetryError{Err: err} 142 } 143 return nil 144 }) 145 146 return err 147 } 148 149 func resourceAwsLambdaPermissionRead(d *schema.ResourceData, meta interface{}) error { 150 conn := meta.(*AWSClient).lambdaconn 151 152 input := lambda.GetPolicyInput{ 153 FunctionName: aws.String(d.Get("function_name").(string)), 154 } 155 if v, ok := d.GetOk("qualifier"); ok { 156 input.Qualifier = aws.String(v.(string)) 157 } 158 159 log.Printf("[DEBUG] Looking for Lambda permission: %s", input) 160 var out *lambda.GetPolicyOutput 161 var statement *LambdaPolicyStatement 162 err := resource.Retry(1*time.Minute, func() error { 163 // IAM is eventually cosistent :/ 164 var err error 165 out, err = conn.GetPolicy(&input) 166 if err != nil { 167 if awsErr, ok := err.(awserr.Error); ok { 168 if awsErr.Code() == "ResourceNotFoundException" { 169 return err 170 } 171 } 172 return resource.RetryError{Err: err} 173 } 174 175 policyInBytes := []byte(*out.Policy) 176 policy := LambdaPolicy{} 177 err = json.Unmarshal(policyInBytes, &policy) 178 if err != nil { 179 return resource.RetryError{Err: fmt.Errorf("Error unmarshalling Lambda policy: %s", err)} 180 } 181 182 statement, err = findLambdaPolicyStatementById(&policy, d.Id()) 183 return err 184 }) 185 if err != nil { 186 return err 187 } 188 189 qualifier, err := getQualifierFromLambdaAliasOrVersionArn(statement.Resource) 190 if err == nil { 191 d.Set("qualifier", qualifier) 192 } 193 194 // Save Lambda function name in the same format 195 if strings.HasPrefix(d.Get("function_name").(string), "arn:aws:lambda:") { 196 // Strip qualifier off 197 trimmedArn := strings.TrimSuffix(statement.Resource, ":"+qualifier) 198 d.Set("function_name", trimmedArn) 199 } else { 200 functionName, err := getFunctionNameFromLambdaArn(statement.Resource) 201 if err != nil { 202 return err 203 } 204 d.Set("function_name", functionName) 205 } 206 207 d.Set("action", statement.Action) 208 d.Set("principal", statement.Principal["Service"]) 209 210 if stringEquals, ok := statement.Condition["StringEquals"]; ok { 211 d.Set("source_account", stringEquals["AWS:SourceAccount"]) 212 } 213 214 if arnLike, ok := statement.Condition["ArnLike"]; ok { 215 d.Set("source_arn", arnLike["AWS:SourceArn"]) 216 } 217 218 return nil 219 } 220 221 func resourceAwsLambdaPermissionDelete(d *schema.ResourceData, meta interface{}) error { 222 conn := meta.(*AWSClient).lambdaconn 223 224 functionName := d.Get("function_name").(string) 225 226 // There is a bug in the API (reported and acknowledged by AWS) 227 // which causes some permissions to be ignored when API calls are sent in parallel 228 // We work around this bug via mutex 229 awsMutexKV.Lock(functionName) 230 defer awsMutexKV.Unlock(functionName) 231 232 input := lambda.RemovePermissionInput{ 233 FunctionName: aws.String(functionName), 234 StatementId: aws.String(d.Id()), 235 } 236 237 if v, ok := d.GetOk("qualifier"); ok { 238 input.Qualifier = aws.String(v.(string)) 239 } 240 241 log.Printf("[DEBUG] Removing Lambda permission: %s", input) 242 _, err := conn.RemovePermission(&input) 243 if err != nil { 244 return err 245 } 246 247 err = resource.Retry(5*time.Minute, func() error { 248 log.Printf("[DEBUG] Checking if Lambda permission %q is deleted", d.Id()) 249 250 params := &lambda.GetPolicyInput{ 251 FunctionName: aws.String(d.Get("function_name").(string)), 252 } 253 if v, ok := d.GetOk("qualifier"); ok { 254 params.Qualifier = aws.String(v.(string)) 255 } 256 257 log.Printf("[DEBUG] Looking for Lambda permission: %s", *params) 258 resp, err := conn.GetPolicy(params) 259 if err != nil { 260 if awsErr, ok := err.(awserr.Error); ok { 261 if awsErr.Code() == "ResourceNotFoundException" { 262 return nil 263 } 264 } 265 return resource.RetryError{Err: err} 266 } 267 268 if resp.Policy == nil { 269 return nil 270 } 271 272 policyInBytes := []byte(*resp.Policy) 273 policy := LambdaPolicy{} 274 err = json.Unmarshal(policyInBytes, &policy) 275 if err != nil { 276 return fmt.Errorf("Error unmarshalling Lambda policy: %s", err) 277 } 278 279 _, err = findLambdaPolicyStatementById(&policy, d.Id()) 280 if err != nil { 281 return nil 282 } 283 284 log.Printf("[DEBUG] No error when checking if Lambda permission %s is deleted", d.Id()) 285 return nil 286 }) 287 288 if err != nil { 289 return fmt.Errorf("Failed removing Lambda permission: %s", err) 290 } 291 292 log.Printf("[DEBUG] Lambda permission with ID %q removed", d.Id()) 293 d.SetId("") 294 295 return nil 296 } 297 298 func findLambdaPolicyStatementById(policy *LambdaPolicy, id string) ( 299 *LambdaPolicyStatement, error) { 300 301 log.Printf("[DEBUG] Received %d statements in Lambda policy: %s", len(policy.Statement), policy.Statement) 302 for _, statement := range policy.Statement { 303 if statement.Sid == id { 304 return &statement, nil 305 } 306 } 307 308 return nil, fmt.Errorf("Failed to find statement %q in Lambda policy:\n%s", id, policy.Statement) 309 } 310 311 func getQualifierFromLambdaAliasOrVersionArn(arn string) (string, error) { 312 matches := regexp.MustCompile(LambdaFunctionRegexp).FindStringSubmatch(arn) 313 if len(matches) < 8 || matches[7] == "" { 314 return "", fmt.Errorf("Invalid ARN or otherwise unable to get qualifier from ARN (%q)", 315 arn) 316 } 317 318 return matches[7], nil 319 } 320 321 func getFunctionNameFromLambdaArn(arn string) (string, error) { 322 matches := regexp.MustCompile(LambdaFunctionRegexp).FindStringSubmatch(arn) 323 if len(matches) < 6 || matches[5] == "" { 324 return "", fmt.Errorf("Invalid ARN or otherwise unable to get qualifier from ARN (%q)", 325 arn) 326 } 327 return matches[5], nil 328 } 329 330 type LambdaPolicy struct { 331 Version string 332 Statement []LambdaPolicyStatement 333 Id string 334 } 335 336 type LambdaPolicyStatement struct { 337 Condition map[string]map[string]string 338 Action string 339 Resource string 340 Effect string 341 Principal map[string]string 342 Sid string 343 }