Skip to content

Commit

Permalink
Merge pull request #4826 from TimeIncOSS/f-aws-lambda-permission
Browse files Browse the repository at this point in the history
[WIP] provider/aws: Add aws_lambda_permission
  • Loading branch information
stack72 committed Feb 17, 2016
2 parents ccfd314 + f8fac71 commit cd28433
Show file tree
Hide file tree
Showing 8 changed files with 1,474 additions and 1 deletion.
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ func Provider() terraform.ResourceProvider {
"aws_lambda_function": resourceAwsLambdaFunction(),
"aws_lambda_event_source_mapping": resourceAwsLambdaEventSourceMapping(),
"aws_lambda_alias": resourceAwsLambdaAlias(),
"aws_lambda_permission": resourceAwsLambdaPermission(),
"aws_launch_configuration": resourceAwsLaunchConfiguration(),
"aws_lb_cookie_stickiness_policy": resourceAwsLBCookieStickinessPolicy(),
"aws_main_route_table_association": resourceAwsMainRouteTableAssociation(),
Expand Down
343 changes: 343 additions & 0 deletions builtin/providers/aws/resource_aws_lambda_permission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,343 @@
package aws

import (
"encoding/json"
"fmt"
"log"
"regexp"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

var LambdaFunctionRegexp = `^(arn:aws:lambda:)?([a-z]{2}-[a-z]+-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\$LATEST|[a-zA-Z0-9-_]+))?$`

func resourceAwsLambdaPermission() *schema.Resource {
return &schema.Resource{
Create: resourceAwsLambdaPermissionCreate,
Read: resourceAwsLambdaPermissionRead,
Delete: resourceAwsLambdaPermissionDelete,

Schema: map[string]*schema.Schema{
"action": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateLambdaPermissionAction,
},
"function_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateLambdaFunctionName,
},
"principal": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"qualifier": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validateLambdaQualifier,
},
"source_account": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validateAwsAccountId,
},
"source_arn": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validateArn,
},
"statement_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validatePolicyStatementId,
},
},
}
}

func resourceAwsLambdaPermissionCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).lambdaconn

functionName := d.Get("function_name").(string)

// There is a bug in the API (reported and acknowledged by AWS)
// which causes some permissions to be ignored when API calls are sent in parallel
// We work around this bug via mutex
awsMutexKV.Lock(functionName)
defer awsMutexKV.Unlock(functionName)

input := lambda.AddPermissionInput{
Action: aws.String(d.Get("action").(string)),
FunctionName: aws.String(functionName),
Principal: aws.String(d.Get("principal").(string)),
StatementId: aws.String(d.Get("statement_id").(string)),
}

if v, ok := d.GetOk("qualifier"); ok {
input.Qualifier = aws.String(v.(string))
}
if v, ok := d.GetOk("source_account"); ok {
input.SourceAccount = aws.String(v.(string))
}
if v, ok := d.GetOk("source_arn"); ok {
input.SourceArn = aws.String(v.(string))
}

log.Printf("[DEBUG] Adding new Lambda permission: %s", input)
var out *lambda.AddPermissionOutput
err := resource.Retry(1*time.Minute, func() error {
var err error
out, err = conn.AddPermission(&input)

if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
// IAM is eventually consistent :/
if awsErr.Code() == "ResourceConflictException" {
return fmt.Errorf("[WARN] Error adding new Lambda Permission for %s, retrying: %s",
*input.FunctionName, err)
}
}
return resource.RetryError{Err: err}
}
return nil
})

if err != nil {
return err
}

log.Printf("[DEBUG] Created new Lambda permission: %s", *out.Statement)

d.SetId(d.Get("statement_id").(string))

err = resource.Retry(5*time.Minute, func() error {
// IAM is eventually cosistent :/
err := resourceAwsLambdaPermissionRead(d, meta)
if err != nil {
if strings.HasPrefix(err.Error(), "Error reading Lambda policy: ResourceNotFoundException") {
return fmt.Errorf("[WARN] Error reading newly created Lambda Permission for %s, retrying: %s",
*input.FunctionName, err)
}
if strings.HasPrefix(err.Error(), "Failed to find statement \""+d.Id()) {
return fmt.Errorf("[WARN] Error reading newly created Lambda Permission statement for %s, retrying: %s",
*input.FunctionName, err)
}

log.Printf("[ERROR] An actual error occured when expecting Lambda policy to be there: %s", err)
return resource.RetryError{Err: err}
}
return nil
})

return err
}

func resourceAwsLambdaPermissionRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).lambdaconn

input := lambda.GetPolicyInput{
FunctionName: aws.String(d.Get("function_name").(string)),
}
if v, ok := d.GetOk("qualifier"); ok {
input.Qualifier = aws.String(v.(string))
}

log.Printf("[DEBUG] Looking for Lambda permission: %s", input)
var out *lambda.GetPolicyOutput
var statement *LambdaPolicyStatement
err := resource.Retry(1*time.Minute, func() error {
// IAM is eventually cosistent :/
var err error
out, err = conn.GetPolicy(&input)
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "ResourceNotFoundException" {
return err
}
}
return resource.RetryError{Err: err}
}

policyInBytes := []byte(*out.Policy)
policy := LambdaPolicy{}
err = json.Unmarshal(policyInBytes, &policy)
if err != nil {
return resource.RetryError{Err: fmt.Errorf("Error unmarshalling Lambda policy: %s", err)}
}

statement, err = findLambdaPolicyStatementById(&policy, d.Id())
return err
})
if err != nil {
return err
}

qualifier, err := getQualifierFromLambdaAliasOrVersionArn(statement.Resource)
if err == nil {
d.Set("qualifier", qualifier)
}

// Save Lambda function name in the same format
if strings.HasPrefix(d.Get("function_name").(string), "arn:aws:lambda:") {
// Strip qualifier off
trimmedArn := strings.TrimSuffix(statement.Resource, ":"+qualifier)
d.Set("function_name", trimmedArn)
} else {
functionName, err := getFunctionNameFromLambdaArn(statement.Resource)
if err != nil {
return err
}
d.Set("function_name", functionName)
}

d.Set("action", statement.Action)
d.Set("principal", statement.Principal["Service"])

if stringEquals, ok := statement.Condition["StringEquals"]; ok {
d.Set("source_account", stringEquals["AWS:SourceAccount"])
}

if arnLike, ok := statement.Condition["ArnLike"]; ok {
d.Set("source_arn", arnLike["AWS:SourceArn"])
}

return nil
}

func resourceAwsLambdaPermissionDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).lambdaconn

functionName := d.Get("function_name").(string)

// There is a bug in the API (reported and acknowledged by AWS)
// which causes some permissions to be ignored when API calls are sent in parallel
// We work around this bug via mutex
awsMutexKV.Lock(functionName)
defer awsMutexKV.Unlock(functionName)

input := lambda.RemovePermissionInput{
FunctionName: aws.String(functionName),
StatementId: aws.String(d.Id()),
}

if v, ok := d.GetOk("qualifier"); ok {
input.Qualifier = aws.String(v.(string))
}

log.Printf("[DEBUG] Removing Lambda permission: %s", input)
_, err := conn.RemovePermission(&input)
if err != nil {
return err
}

err = resource.Retry(5*time.Minute, func() error {
log.Printf("[DEBUG] Checking if Lambda permission %q is deleted", d.Id())

params := &lambda.GetPolicyInput{
FunctionName: aws.String(d.Get("function_name").(string)),
}
if v, ok := d.GetOk("qualifier"); ok {
params.Qualifier = aws.String(v.(string))
}

log.Printf("[DEBUG] Looking for Lambda permission: %s", *params)
resp, err := conn.GetPolicy(params)
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "ResourceNotFoundException" {
return nil
}
}
return resource.RetryError{Err: err}
}

if resp.Policy == nil {
return nil
}

policyInBytes := []byte(*resp.Policy)
policy := LambdaPolicy{}
err = json.Unmarshal(policyInBytes, &policy)
if err != nil {
return fmt.Errorf("Error unmarshalling Lambda policy: %s", err)
}

_, err = findLambdaPolicyStatementById(&policy, d.Id())
if err != nil {
return nil
}

log.Printf("[DEBUG] No error when checking if Lambda permission %s is deleted", d.Id())
return nil
})

if err != nil {
return fmt.Errorf("Failed removing Lambda permission: %s", err)
}

log.Printf("[DEBUG] Lambda permission with ID %q removed", d.Id())
d.SetId("")

return nil
}

func findLambdaPolicyStatementById(policy *LambdaPolicy, id string) (
*LambdaPolicyStatement, error) {

log.Printf("[DEBUG] Received %d statements in Lambda policy: %s", len(policy.Statement), policy.Statement)
for _, statement := range policy.Statement {
if statement.Sid == id {
return &statement, nil
}
}

return nil, fmt.Errorf("Failed to find statement %q in Lambda policy:\n%s", id, policy.Statement)
}

func getQualifierFromLambdaAliasOrVersionArn(arn string) (string, error) {
matches := regexp.MustCompile(LambdaFunctionRegexp).FindStringSubmatch(arn)
if len(matches) < 8 || matches[7] == "" {
return "", fmt.Errorf("Invalid ARN or otherwise unable to get qualifier from ARN (%q)",
arn)
}

return matches[7], nil
}

func getFunctionNameFromLambdaArn(arn string) (string, error) {
matches := regexp.MustCompile(LambdaFunctionRegexp).FindStringSubmatch(arn)
if len(matches) < 6 || matches[5] == "" {
return "", fmt.Errorf("Invalid ARN or otherwise unable to get qualifier from ARN (%q)",
arn)
}
return matches[5], nil
}

type LambdaPolicy struct {
Version string
Statement []LambdaPolicyStatement
Id string
}

type LambdaPolicyStatement struct {
Condition map[string]map[string]string
Action string
Resource string
Effect string
Principal map[string]string
Sid string
}
Loading

0 comments on commit cd28433

Please sign in to comment.