github.com/gabrielperezs/terraform@v0.7.0-rc2.0.20160715084931-f7da2612946f/builtin/providers/aws/resource_aws_lambda_function.go (about)

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