Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/aws: Add aws_lambda_permission #4826

Merged
merged 7 commits into from
Feb 17, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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