github.com/articulate/terraform@v0.6.13-0.20160303003731-8d31c93862de/builtin/providers/aws/resource_aws_lambda_function.go (about)

     1  package aws
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/aws/awserr"
    12  	"github.com/aws/aws-sdk-go/service/lambda"
    13  	"github.com/mitchellh/go-homedir"
    14  
    15  	"errors"
    16  
    17  	"github.com/hashicorp/terraform/helper/resource"
    18  	"github.com/hashicorp/terraform/helper/schema"
    19  )
    20  
    21  func resourceAwsLambdaFunction() *schema.Resource {
    22  	return &schema.Resource{
    23  		Create: resourceAwsLambdaFunctionCreate,
    24  		Read:   resourceAwsLambdaFunctionRead,
    25  		Update: resourceAwsLambdaFunctionUpdate,
    26  		Delete: resourceAwsLambdaFunctionDelete,
    27  
    28  		Schema: map[string]*schema.Schema{
    29  			"filename": &schema.Schema{
    30  				Type:          schema.TypeString,
    31  				Optional:      true,
    32  				ConflictsWith: []string{"s3_bucket", "s3_key", "s3_object_version"},
    33  			},
    34  			"s3_bucket": &schema.Schema{
    35  				Type:          schema.TypeString,
    36  				Optional:      true,
    37  				ConflictsWith: []string{"filename"},
    38  			},
    39  			"s3_key": &schema.Schema{
    40  				Type:          schema.TypeString,
    41  				Optional:      true,
    42  				ConflictsWith: []string{"filename"},
    43  			},
    44  			"s3_object_version": &schema.Schema{
    45  				Type:          schema.TypeString,
    46  				Optional:      true,
    47  				ConflictsWith: []string{"filename"},
    48  			},
    49  			"description": &schema.Schema{
    50  				Type:     schema.TypeString,
    51  				Optional: true,
    52  			},
    53  			"function_name": &schema.Schema{
    54  				Type:     schema.TypeString,
    55  				Required: true,
    56  				ForceNew: true,
    57  			},
    58  			"handler": &schema.Schema{
    59  				Type:     schema.TypeString,
    60  				Required: true,
    61  				ForceNew: true, // TODO make this editable
    62  			},
    63  			"memory_size": &schema.Schema{
    64  				Type:     schema.TypeInt,
    65  				Optional: true,
    66  				Default:  128,
    67  				ForceNew: true, // TODO make this editable
    68  			},
    69  			"role": &schema.Schema{
    70  				Type:     schema.TypeString,
    71  				Required: true,
    72  				ForceNew: true, // TODO make this editable
    73  			},
    74  			"runtime": &schema.Schema{
    75  				Type:     schema.TypeString,
    76  				Optional: true,
    77  				ForceNew: true,
    78  				Default:  "nodejs",
    79  			},
    80  			"timeout": &schema.Schema{
    81  				Type:     schema.TypeInt,
    82  				Optional: true,
    83  				Default:  3,
    84  				ForceNew: true, // TODO make this editable
    85  			},
    86  			"vpc_config": &schema.Schema{
    87  				Type:     schema.TypeList,
    88  				Optional: true,
    89  				ForceNew: true,
    90  				Elem: &schema.Resource{
    91  					Schema: map[string]*schema.Schema{
    92  						"subnet_ids": &schema.Schema{
    93  							Type:     schema.TypeSet,
    94  							Required: true,
    95  							ForceNew: true,
    96  							Elem:     &schema.Schema{Type: schema.TypeString},
    97  							Set:      schema.HashString,
    98  						},
    99  						"security_group_ids": &schema.Schema{
   100  							Type:     schema.TypeSet,
   101  							Required: true,
   102  							ForceNew: true,
   103  							Elem:     &schema.Schema{Type: schema.TypeString},
   104  							Set:      schema.HashString,
   105  						},
   106  					},
   107  				},
   108  			},
   109  			"arn": &schema.Schema{
   110  				Type:     schema.TypeString,
   111  				Computed: true,
   112  			},
   113  			"last_modified": &schema.Schema{
   114  				Type:     schema.TypeString,
   115  				Computed: true,
   116  			},
   117  			"source_code_hash": &schema.Schema{
   118  				Type:     schema.TypeString,
   119  				Computed: true,
   120  				ForceNew: true,
   121  			},
   122  		},
   123  	}
   124  }
   125  
   126  // resourceAwsLambdaFunction maps to:
   127  // CreateFunction in the API / SDK
   128  func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) error {
   129  	conn := meta.(*AWSClient).lambdaconn
   130  
   131  	functionName := d.Get("function_name").(string)
   132  	iamRole := d.Get("role").(string)
   133  
   134  	log.Printf("[DEBUG] Creating Lambda Function %s with role %s", functionName, iamRole)
   135  
   136  	var functionCode *lambda.FunctionCode
   137  	if v, ok := d.GetOk("filename"); ok {
   138  		filename, err := homedir.Expand(v.(string))
   139  		if err != nil {
   140  			return err
   141  		}
   142  		zipfile, err := ioutil.ReadFile(filename)
   143  		if err != nil {
   144  			return err
   145  		}
   146  		d.Set("source_code_hash", sha256.Sum256(zipfile))
   147  		functionCode = &lambda.FunctionCode{
   148  			ZipFile: zipfile,
   149  		}
   150  	} else {
   151  		s3Bucket, bucketOk := d.GetOk("s3_bucket")
   152  		s3Key, keyOk := d.GetOk("s3_key")
   153  		s3ObjectVersion, versionOk := d.GetOk("s3_object_version")
   154  		if !bucketOk || !keyOk {
   155  			return errors.New("s3_bucket and s3_key must all be set while using S3 code source")
   156  		}
   157  		functionCode = &lambda.FunctionCode{
   158  			S3Bucket: aws.String(s3Bucket.(string)),
   159  			S3Key:    aws.String(s3Key.(string)),
   160  		}
   161  		if versionOk {
   162  			functionCode.S3ObjectVersion = aws.String(s3ObjectVersion.(string))
   163  		}
   164  	}
   165  
   166  	params := &lambda.CreateFunctionInput{
   167  		Code:         functionCode,
   168  		Description:  aws.String(d.Get("description").(string)),
   169  		FunctionName: aws.String(functionName),
   170  		Handler:      aws.String(d.Get("handler").(string)),
   171  		MemorySize:   aws.Int64(int64(d.Get("memory_size").(int))),
   172  		Role:         aws.String(iamRole),
   173  		Runtime:      aws.String(d.Get("runtime").(string)),
   174  		Timeout:      aws.Int64(int64(d.Get("timeout").(int))),
   175  	}
   176  
   177  	if v, ok := d.GetOk("vpc_config"); ok {
   178  		config, err := validateVPCConfig(v)
   179  		if err != nil {
   180  			return err
   181  		}
   182  
   183  		var subnetIds []*string
   184  		for _, id := range config["subnet_ids"].(*schema.Set).List() {
   185  			subnetIds = append(subnetIds, aws.String(id.(string)))
   186  		}
   187  
   188  		var securityGroupIds []*string
   189  		for _, id := range config["security_group_ids"].(*schema.Set).List() {
   190  			securityGroupIds = append(securityGroupIds, aws.String(id.(string)))
   191  		}
   192  
   193  		params.VpcConfig = &lambda.VpcConfig{
   194  			SubnetIds:        subnetIds,
   195  			SecurityGroupIds: securityGroupIds,
   196  		}
   197  	}
   198  
   199  	// IAM profiles can take ~10 seconds to propagate in AWS:
   200  	// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console
   201  	// Error creating Lambda function: InvalidParameterValueException: The role defined for the task cannot be assumed by Lambda.
   202  	err := resource.Retry(1*time.Minute, func() error {
   203  		_, err := conn.CreateFunction(params)
   204  		if err != nil {
   205  			if awserr, ok := err.(awserr.Error); ok {
   206  				if awserr.Code() == "InvalidParameterValueException" {
   207  					log.Printf("[DEBUG] InvalidParameterValueException creating Lambda Function: %s", awserr)
   208  					// Retryable
   209  					return awserr
   210  				}
   211  			}
   212  			log.Printf("[DEBUG] Error creating Lambda Function: %s", err)
   213  			// Not retryable
   214  			return resource.RetryError{Err: err}
   215  		}
   216  		// No error
   217  		return nil
   218  	})
   219  	if err != nil {
   220  		return fmt.Errorf("Error creating Lambda function: %s", err)
   221  	}
   222  
   223  	d.SetId(d.Get("function_name").(string))
   224  
   225  	return resourceAwsLambdaFunctionRead(d, meta)
   226  }
   227  
   228  // resourceAwsLambdaFunctionRead maps to:
   229  // GetFunction in the API / SDK
   230  func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) error {
   231  	conn := meta.(*AWSClient).lambdaconn
   232  
   233  	log.Printf("[DEBUG] Fetching Lambda Function: %s", d.Id())
   234  
   235  	params := &lambda.GetFunctionInput{
   236  		FunctionName: aws.String(d.Get("function_name").(string)),
   237  	}
   238  
   239  	getFunctionOutput, err := conn.GetFunction(params)
   240  	if err != nil {
   241  		return err
   242  	}
   243  
   244  	// getFunctionOutput.Code.Location is a pre-signed URL pointing at the zip
   245  	// file that we uploaded when we created the resource. You can use it to
   246  	// download the code from AWS. The other part is
   247  	// getFunctionOutput.Configuration which holds metadata.
   248  
   249  	function := getFunctionOutput.Configuration
   250  	// TODO error checking / handling on the Set() calls.
   251  	d.Set("arn", function.FunctionArn)
   252  	d.Set("description", function.Description)
   253  	d.Set("handler", function.Handler)
   254  	d.Set("memory_size", function.MemorySize)
   255  	d.Set("last_modified", function.LastModified)
   256  	d.Set("role", function.Role)
   257  	d.Set("runtime", function.Runtime)
   258  	d.Set("timeout", function.Timeout)
   259  	if config := flattenLambdaVpcConfigResponse(function.VpcConfig); len(config) > 0 {
   260  		d.Set("vpc_config", config)
   261  	}
   262  
   263  	return nil
   264  }
   265  
   266  // resourceAwsLambdaFunction maps to:
   267  // DeleteFunction in the API / SDK
   268  func resourceAwsLambdaFunctionDelete(d *schema.ResourceData, meta interface{}) error {
   269  	conn := meta.(*AWSClient).lambdaconn
   270  
   271  	log.Printf("[INFO] Deleting Lambda Function: %s", d.Id())
   272  
   273  	params := &lambda.DeleteFunctionInput{
   274  		FunctionName: aws.String(d.Get("function_name").(string)),
   275  	}
   276  
   277  	_, err := conn.DeleteFunction(params)
   278  	if err != nil {
   279  		return fmt.Errorf("Error deleting Lambda Function: %s", err)
   280  	}
   281  
   282  	d.SetId("")
   283  
   284  	return nil
   285  }
   286  
   287  // resourceAwsLambdaFunctionUpdate maps to:
   288  // UpdateFunctionCode in the API / SDK
   289  func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) error {
   290  	return nil
   291  }
   292  
   293  func validateVPCConfig(v interface{}) (map[string]interface{}, error) {
   294  	configs := v.([]interface{})
   295  	if len(configs) > 1 {
   296  		return nil, errors.New("Only a single vpc_config block is expected")
   297  	}
   298  
   299  	config, ok := configs[0].(map[string]interface{})
   300  
   301  	if !ok {
   302  		return nil, errors.New("vpc_config is <nil>")
   303  	}
   304  
   305  	if config["subnet_ids"].(*schema.Set).Len() == 0 {
   306  		return nil, errors.New("vpc_config.subnet_ids cannot be empty")
   307  	}
   308  
   309  	if config["security_group_ids"].(*schema.Set).Len() == 0 {
   310  		return nil, errors.New("vpc_config.security_group_ids cannot be empty")
   311  	}
   312  
   313  	return config, nil
   314  }