github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/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  	"github.com/hashicorp/terraform/helper/validation"
    19  )
    20  
    21  const awsMutexLambdaKey = `aws_lambda_function`
    22  
    23  func resourceAwsLambdaFunction() *schema.Resource {
    24  	return &schema.Resource{
    25  		Create: resourceAwsLambdaFunctionCreate,
    26  		Read:   resourceAwsLambdaFunctionRead,
    27  		Update: resourceAwsLambdaFunctionUpdate,
    28  		Delete: resourceAwsLambdaFunctionDelete,
    29  
    30  		Importer: &schema.ResourceImporter{
    31  			State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
    32  				d.Set("function_name", d.Id())
    33  				return []*schema.ResourceData{d}, nil
    34  			},
    35  		},
    36  
    37  		Schema: map[string]*schema.Schema{
    38  			"filename": {
    39  				Type:          schema.TypeString,
    40  				Optional:      true,
    41  				ConflictsWith: []string{"s3_bucket", "s3_key", "s3_object_version"},
    42  			},
    43  			"s3_bucket": {
    44  				Type:          schema.TypeString,
    45  				Optional:      true,
    46  				ConflictsWith: []string{"filename"},
    47  			},
    48  			"s3_key": {
    49  				Type:          schema.TypeString,
    50  				Optional:      true,
    51  				ConflictsWith: []string{"filename"},
    52  			},
    53  			"s3_object_version": {
    54  				Type:          schema.TypeString,
    55  				Optional:      true,
    56  				ConflictsWith: []string{"filename"},
    57  			},
    58  			"description": {
    59  				Type:     schema.TypeString,
    60  				Optional: true,
    61  			},
    62  			"dead_letter_config": {
    63  				Type:     schema.TypeList,
    64  				Optional: true,
    65  				ForceNew: true,
    66  				MinItems: 0,
    67  				MaxItems: 1,
    68  				Elem: &schema.Resource{
    69  					Schema: map[string]*schema.Schema{
    70  						"target_arn": {
    71  							Type:         schema.TypeString,
    72  							Required:     true,
    73  							ValidateFunc: validateArn,
    74  						},
    75  					},
    76  				},
    77  			},
    78  			"function_name": {
    79  				Type:     schema.TypeString,
    80  				Required: true,
    81  				ForceNew: true,
    82  			},
    83  			"handler": {
    84  				Type:     schema.TypeString,
    85  				Required: true,
    86  			},
    87  			"memory_size": {
    88  				Type:     schema.TypeInt,
    89  				Optional: true,
    90  				Default:  128,
    91  			},
    92  			"role": {
    93  				Type:     schema.TypeString,
    94  				Required: true,
    95  			},
    96  			"runtime": {
    97  				Type:         schema.TypeString,
    98  				Required:     true,
    99  				ValidateFunc: validateRuntime,
   100  			},
   101  			"timeout": {
   102  				Type:     schema.TypeInt,
   103  				Optional: true,
   104  				Default:  3,
   105  			},
   106  			"publish": {
   107  				Type:     schema.TypeBool,
   108  				Optional: true,
   109  				Default:  false,
   110  			},
   111  			"version": {
   112  				Type:     schema.TypeString,
   113  				Computed: true,
   114  			},
   115  			"vpc_config": {
   116  				Type:     schema.TypeList,
   117  				Optional: true,
   118  				ForceNew: true,
   119  				Elem: &schema.Resource{
   120  					Schema: map[string]*schema.Schema{
   121  						"subnet_ids": {
   122  							Type:     schema.TypeSet,
   123  							Required: true,
   124  							ForceNew: true,
   125  							Elem:     &schema.Schema{Type: schema.TypeString},
   126  							Set:      schema.HashString,
   127  						},
   128  						"security_group_ids": {
   129  							Type:     schema.TypeSet,
   130  							Required: true,
   131  							ForceNew: true,
   132  							Elem:     &schema.Schema{Type: schema.TypeString},
   133  							Set:      schema.HashString,
   134  						},
   135  						"vpc_id": {
   136  							Type:     schema.TypeString,
   137  							Computed: true,
   138  						},
   139  					},
   140  				},
   141  			},
   142  			"arn": {
   143  				Type:     schema.TypeString,
   144  				Computed: true,
   145  			},
   146  			"qualified_arn": {
   147  				Type:     schema.TypeString,
   148  				Computed: true,
   149  			},
   150  			"invoke_arn": {
   151  				Type:     schema.TypeString,
   152  				Computed: true,
   153  			},
   154  			"last_modified": {
   155  				Type:     schema.TypeString,
   156  				Computed: true,
   157  			},
   158  			"source_code_hash": {
   159  				Type:     schema.TypeString,
   160  				Optional: true,
   161  				Computed: true,
   162  			},
   163  			"environment": {
   164  				Type:     schema.TypeList,
   165  				Optional: true,
   166  				MaxItems: 1,
   167  				Elem: &schema.Resource{
   168  					Schema: map[string]*schema.Schema{
   169  						"variables": {
   170  							Type:     schema.TypeMap,
   171  							Optional: true,
   172  							Elem:     schema.TypeString,
   173  						},
   174  					},
   175  				},
   176  			},
   177  
   178  			"tracing_config": {
   179  				Type:     schema.TypeList,
   180  				MaxItems: 1,
   181  				Optional: true,
   182  				Computed: true,
   183  				Elem: &schema.Resource{
   184  					Schema: map[string]*schema.Schema{
   185  						"mode": {
   186  							Type:         schema.TypeString,
   187  							Required:     true,
   188  							ValidateFunc: validation.StringInSlice([]string{"Active", "PassThrough"}, true),
   189  						},
   190  					},
   191  				},
   192  			},
   193  
   194  			"kms_key_arn": {
   195  				Type:         schema.TypeString,
   196  				Optional:     true,
   197  				ValidateFunc: validateArn,
   198  			},
   199  
   200  			"tags": tagsSchema(),
   201  		},
   202  	}
   203  }
   204  
   205  // resourceAwsLambdaFunction maps to:
   206  // CreateFunction in the API / SDK
   207  func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) error {
   208  	conn := meta.(*AWSClient).lambdaconn
   209  
   210  	functionName := d.Get("function_name").(string)
   211  	iamRole := d.Get("role").(string)
   212  
   213  	log.Printf("[DEBUG] Creating Lambda Function %s with role %s", functionName, iamRole)
   214  
   215  	filename, hasFilename := d.GetOk("filename")
   216  	s3Bucket, bucketOk := d.GetOk("s3_bucket")
   217  	s3Key, keyOk := d.GetOk("s3_key")
   218  	s3ObjectVersion, versionOk := d.GetOk("s3_object_version")
   219  
   220  	if !hasFilename && !bucketOk && !keyOk && !versionOk {
   221  		return errors.New("filename or s3_* attributes must be set")
   222  	}
   223  
   224  	var functionCode *lambda.FunctionCode
   225  	if hasFilename {
   226  		// Grab an exclusive lock so that we're only reading one function into
   227  		// memory at a time.
   228  		// See https://github.com/hashicorp/terraform/issues/9364
   229  		awsMutexKV.Lock(awsMutexLambdaKey)
   230  		defer awsMutexKV.Unlock(awsMutexLambdaKey)
   231  		file, err := loadFileContent(filename.(string))
   232  		if err != nil {
   233  			return fmt.Errorf("Unable to load %q: %s", filename.(string), err)
   234  		}
   235  		functionCode = &lambda.FunctionCode{
   236  			ZipFile: file,
   237  		}
   238  	} else {
   239  		if !bucketOk || !keyOk {
   240  			return errors.New("s3_bucket and s3_key must all be set while using S3 code source")
   241  		}
   242  		functionCode = &lambda.FunctionCode{
   243  			S3Bucket: aws.String(s3Bucket.(string)),
   244  			S3Key:    aws.String(s3Key.(string)),
   245  		}
   246  		if versionOk {
   247  			functionCode.S3ObjectVersion = aws.String(s3ObjectVersion.(string))
   248  		}
   249  	}
   250  
   251  	params := &lambda.CreateFunctionInput{
   252  		Code:         functionCode,
   253  		Description:  aws.String(d.Get("description").(string)),
   254  		FunctionName: aws.String(functionName),
   255  		Handler:      aws.String(d.Get("handler").(string)),
   256  		MemorySize:   aws.Int64(int64(d.Get("memory_size").(int))),
   257  		Role:         aws.String(iamRole),
   258  		Runtime:      aws.String(d.Get("runtime").(string)),
   259  		Timeout:      aws.Int64(int64(d.Get("timeout").(int))),
   260  		Publish:      aws.Bool(d.Get("publish").(bool)),
   261  	}
   262  
   263  	if v, ok := d.GetOk("dead_letter_config"); ok {
   264  		dlcMaps := v.([]interface{})
   265  		if len(dlcMaps) == 1 { // Schema guarantees either 0 or 1
   266  			// Prevent panic on nil dead_letter_config. See GH-14961
   267  			if dlcMaps[0] == nil {
   268  				return fmt.Errorf("Nil dead_letter_config supplied for function: %s", functionName)
   269  			}
   270  			dlcMap := dlcMaps[0].(map[string]interface{})
   271  			params.DeadLetterConfig = &lambda.DeadLetterConfig{
   272  				TargetArn: aws.String(dlcMap["target_arn"].(string)),
   273  			}
   274  		}
   275  	}
   276  
   277  	if v, ok := d.GetOk("vpc_config"); ok {
   278  		config, err := validateVPCConfig(v)
   279  		if err != nil {
   280  			return err
   281  		}
   282  
   283  		if config != nil {
   284  			var subnetIds []*string
   285  			for _, id := range config["subnet_ids"].(*schema.Set).List() {
   286  				subnetIds = append(subnetIds, aws.String(id.(string)))
   287  			}
   288  
   289  			var securityGroupIds []*string
   290  			for _, id := range config["security_group_ids"].(*schema.Set).List() {
   291  				securityGroupIds = append(securityGroupIds, aws.String(id.(string)))
   292  			}
   293  
   294  			params.VpcConfig = &lambda.VpcConfig{
   295  				SubnetIds:        subnetIds,
   296  				SecurityGroupIds: securityGroupIds,
   297  			}
   298  		}
   299  	}
   300  
   301  	if v, ok := d.GetOk("tracing_config"); ok {
   302  		tracingConfig := v.([]interface{})
   303  		tracing := tracingConfig[0].(map[string]interface{})
   304  		params.TracingConfig = &lambda.TracingConfig{
   305  			Mode: aws.String(tracing["mode"].(string)),
   306  		}
   307  	}
   308  
   309  	if v, ok := d.GetOk("environment"); ok {
   310  		environments := v.([]interface{})
   311  		environment, ok := environments[0].(map[string]interface{})
   312  		if !ok {
   313  			return errors.New("At least one field is expected inside environment")
   314  		}
   315  
   316  		if environmentVariables, ok := environment["variables"]; ok {
   317  			variables := readEnvironmentVariables(environmentVariables.(map[string]interface{}))
   318  
   319  			params.Environment = &lambda.Environment{
   320  				Variables: aws.StringMap(variables),
   321  			}
   322  		}
   323  	}
   324  
   325  	if v, ok := d.GetOk("kms_key_arn"); ok {
   326  		params.KMSKeyArn = aws.String(v.(string))
   327  	}
   328  
   329  	if v, exists := d.GetOk("tags"); exists {
   330  		params.Tags = tagsFromMapGeneric(v.(map[string]interface{}))
   331  	}
   332  
   333  	// IAM profiles can take ~10 seconds to propagate in AWS:
   334  	// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console
   335  	// Error creating Lambda function: InvalidParameterValueException: The role defined for the task cannot be assumed by Lambda.
   336  	err := resource.Retry(10*time.Minute, func() *resource.RetryError {
   337  		_, err := conn.CreateFunction(params)
   338  		if err != nil {
   339  			log.Printf("[DEBUG] Error creating Lambda Function: %s", err)
   340  
   341  			if isAWSErr(err, "InvalidParameterValueException", "The role defined for the function cannot be assumed by Lambda") {
   342  				log.Printf("[DEBUG] Received %s, retrying CreateFunction", err)
   343  				return resource.RetryableError(err)
   344  			}
   345  			if isAWSErr(err, "InvalidParameterValueException", "The provided execution role does not have permissions") {
   346  				log.Printf("[DEBUG] Received %s, retrying CreateFunction", err)
   347  				return resource.RetryableError(err)
   348  			}
   349  
   350  			return resource.NonRetryableError(err)
   351  		}
   352  		return nil
   353  	})
   354  	if err != nil {
   355  		return fmt.Errorf("Error creating Lambda function: %s", err)
   356  	}
   357  
   358  	d.SetId(d.Get("function_name").(string))
   359  
   360  	return resourceAwsLambdaFunctionRead(d, meta)
   361  }
   362  
   363  // resourceAwsLambdaFunctionRead maps to:
   364  // GetFunction in the API / SDK
   365  func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) error {
   366  	conn := meta.(*AWSClient).lambdaconn
   367  
   368  	log.Printf("[DEBUG] Fetching Lambda Function: %s", d.Id())
   369  
   370  	params := &lambda.GetFunctionInput{
   371  		FunctionName: aws.String(d.Get("function_name").(string)),
   372  	}
   373  
   374  	getFunctionOutput, err := conn.GetFunction(params)
   375  	if err != nil {
   376  		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" && !d.IsNewResource() {
   377  			d.SetId("")
   378  			return nil
   379  		}
   380  		return err
   381  	}
   382  
   383  	// getFunctionOutput.Code.Location is a pre-signed URL pointing at the zip
   384  	// file that we uploaded when we created the resource. You can use it to
   385  	// download the code from AWS. The other part is
   386  	// getFunctionOutput.Configuration which holds metadata.
   387  
   388  	function := getFunctionOutput.Configuration
   389  	// TODO error checking / handling on the Set() calls.
   390  	d.Set("arn", function.FunctionArn)
   391  	d.Set("description", function.Description)
   392  	d.Set("handler", function.Handler)
   393  	d.Set("memory_size", function.MemorySize)
   394  	d.Set("last_modified", function.LastModified)
   395  	d.Set("role", function.Role)
   396  	d.Set("runtime", function.Runtime)
   397  	d.Set("timeout", function.Timeout)
   398  	d.Set("kms_key_arn", function.KMSKeyArn)
   399  	d.Set("tags", tagsToMapGeneric(getFunctionOutput.Tags))
   400  
   401  	config := flattenLambdaVpcConfigResponse(function.VpcConfig)
   402  	log.Printf("[INFO] Setting Lambda %s VPC config %#v from API", d.Id(), config)
   403  	vpcSetErr := d.Set("vpc_config", config)
   404  	if vpcSetErr != nil {
   405  		return fmt.Errorf("Failed setting vpc_config: %s", vpcSetErr)
   406  	}
   407  
   408  	d.Set("source_code_hash", function.CodeSha256)
   409  
   410  	if err := d.Set("environment", flattenLambdaEnvironment(function.Environment)); err != nil {
   411  		log.Printf("[ERR] Error setting environment for Lambda Function (%s): %s", d.Id(), err)
   412  	}
   413  
   414  	if function.DeadLetterConfig != nil && function.DeadLetterConfig.TargetArn != nil {
   415  		d.Set("dead_letter_config", []interface{}{
   416  			map[string]interface{}{
   417  				"target_arn": *function.DeadLetterConfig.TargetArn,
   418  			},
   419  		})
   420  	} else {
   421  		d.Set("dead_letter_config", []interface{}{})
   422  	}
   423  
   424  	if function.TracingConfig != nil {
   425  		d.Set("tracing_config", []interface{}{
   426  			map[string]interface{}{
   427  				"mode": *function.TracingConfig.Mode,
   428  			},
   429  		})
   430  	}
   431  
   432  	// List is sorted from oldest to latest
   433  	// so this may get costly over time :'(
   434  	var lastVersion, lastQualifiedArn string
   435  	err = listVersionsByFunctionPages(conn, &lambda.ListVersionsByFunctionInput{
   436  		FunctionName: function.FunctionName,
   437  		MaxItems:     aws.Int64(10000),
   438  	}, func(p *lambda.ListVersionsByFunctionOutput, lastPage bool) bool {
   439  		if lastPage {
   440  			last := p.Versions[len(p.Versions)-1]
   441  			lastVersion = *last.Version
   442  			lastQualifiedArn = *last.FunctionArn
   443  			return false
   444  		}
   445  		return true
   446  	})
   447  	if err != nil {
   448  		return err
   449  	}
   450  
   451  	d.Set("version", lastVersion)
   452  	d.Set("qualified_arn", lastQualifiedArn)
   453  
   454  	d.Set("invoke_arn", buildLambdaInvokeArn(*function.FunctionArn, meta.(*AWSClient).region))
   455  
   456  	return nil
   457  }
   458  
   459  func listVersionsByFunctionPages(c *lambda.Lambda, input *lambda.ListVersionsByFunctionInput,
   460  	fn func(p *lambda.ListVersionsByFunctionOutput, lastPage bool) bool) error {
   461  	for {
   462  		page, err := c.ListVersionsByFunction(input)
   463  		if err != nil {
   464  			return err
   465  		}
   466  		lastPage := page.NextMarker == nil
   467  
   468  		shouldContinue := fn(page, lastPage)
   469  		if !shouldContinue || lastPage {
   470  			break
   471  		}
   472  		input.Marker = page.NextMarker
   473  	}
   474  	return nil
   475  }
   476  
   477  // resourceAwsLambdaFunction maps to:
   478  // DeleteFunction in the API / SDK
   479  func resourceAwsLambdaFunctionDelete(d *schema.ResourceData, meta interface{}) error {
   480  	conn := meta.(*AWSClient).lambdaconn
   481  
   482  	log.Printf("[INFO] Deleting Lambda Function: %s", d.Id())
   483  
   484  	params := &lambda.DeleteFunctionInput{
   485  		FunctionName: aws.String(d.Get("function_name").(string)),
   486  	}
   487  
   488  	_, err := conn.DeleteFunction(params)
   489  	if err != nil {
   490  		return fmt.Errorf("Error deleting Lambda Function: %s", err)
   491  	}
   492  
   493  	d.SetId("")
   494  
   495  	return nil
   496  }
   497  
   498  // resourceAwsLambdaFunctionUpdate maps to:
   499  // UpdateFunctionCode in the API / SDK
   500  func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) error {
   501  	conn := meta.(*AWSClient).lambdaconn
   502  
   503  	d.Partial(true)
   504  
   505  	arn := d.Get("arn").(string)
   506  	if tagErr := setTagsLambda(conn, d, arn); tagErr != nil {
   507  		return tagErr
   508  	}
   509  	d.SetPartial("tags")
   510  
   511  	if d.HasChange("filename") || d.HasChange("source_code_hash") || d.HasChange("s3_bucket") || d.HasChange("s3_key") || d.HasChange("s3_object_version") {
   512  		codeReq := &lambda.UpdateFunctionCodeInput{
   513  			FunctionName: aws.String(d.Id()),
   514  			Publish:      aws.Bool(d.Get("publish").(bool)),
   515  		}
   516  
   517  		if v, ok := d.GetOk("filename"); ok {
   518  			// Grab an exclusive lock so that we're only reading one function into
   519  			// memory at a time.
   520  			// See https://github.com/hashicorp/terraform/issues/9364
   521  			awsMutexKV.Lock(awsMutexLambdaKey)
   522  			defer awsMutexKV.Unlock(awsMutexLambdaKey)
   523  			file, err := loadFileContent(v.(string))
   524  			if err != nil {
   525  				return fmt.Errorf("Unable to load %q: %s", v.(string), err)
   526  			}
   527  			codeReq.ZipFile = file
   528  		} else {
   529  			s3Bucket, _ := d.GetOk("s3_bucket")
   530  			s3Key, _ := d.GetOk("s3_key")
   531  			s3ObjectVersion, versionOk := d.GetOk("s3_object_version")
   532  
   533  			codeReq.S3Bucket = aws.String(s3Bucket.(string))
   534  			codeReq.S3Key = aws.String(s3Key.(string))
   535  			if versionOk {
   536  				codeReq.S3ObjectVersion = aws.String(s3ObjectVersion.(string))
   537  			}
   538  		}
   539  
   540  		log.Printf("[DEBUG] Send Update Lambda Function Code request: %#v", codeReq)
   541  
   542  		_, err := conn.UpdateFunctionCode(codeReq)
   543  		if err != nil {
   544  			return fmt.Errorf("Error modifying Lambda Function Code %s: %s", d.Id(), err)
   545  		}
   546  
   547  		d.SetPartial("filename")
   548  		d.SetPartial("source_code_hash")
   549  		d.SetPartial("s3_bucket")
   550  		d.SetPartial("s3_key")
   551  		d.SetPartial("s3_object_version")
   552  	}
   553  
   554  	configReq := &lambda.UpdateFunctionConfigurationInput{
   555  		FunctionName: aws.String(d.Id()),
   556  	}
   557  
   558  	configUpdate := false
   559  	if d.HasChange("description") {
   560  		configReq.Description = aws.String(d.Get("description").(string))
   561  		configUpdate = true
   562  	}
   563  	if d.HasChange("handler") {
   564  		configReq.Handler = aws.String(d.Get("handler").(string))
   565  		configUpdate = true
   566  	}
   567  	if d.HasChange("memory_size") {
   568  		configReq.MemorySize = aws.Int64(int64(d.Get("memory_size").(int)))
   569  		configUpdate = true
   570  	}
   571  	if d.HasChange("role") {
   572  		configReq.Role = aws.String(d.Get("role").(string))
   573  		configUpdate = true
   574  	}
   575  	if d.HasChange("timeout") {
   576  		configReq.Timeout = aws.Int64(int64(d.Get("timeout").(int)))
   577  		configUpdate = true
   578  	}
   579  	if d.HasChange("kms_key_arn") {
   580  		configReq.KMSKeyArn = aws.String(d.Get("kms_key_arn").(string))
   581  		configUpdate = true
   582  	}
   583  	if d.HasChange("dead_letter_config") {
   584  		dlcMaps := d.Get("dead_letter_config").([]interface{})
   585  		if len(dlcMaps) == 1 { // Schema guarantees either 0 or 1
   586  			dlcMap := dlcMaps[0].(map[string]interface{})
   587  			configReq.DeadLetterConfig = &lambda.DeadLetterConfig{
   588  				TargetArn: aws.String(dlcMap["target_arn"].(string)),
   589  			}
   590  			configUpdate = true
   591  		}
   592  	}
   593  	if d.HasChange("tracing_config") {
   594  		tracingConfig := d.Get("tracing_config").([]interface{})
   595  		if len(tracingConfig) == 1 { // Schema guarantees either 0 or 1
   596  			config := tracingConfig[0].(map[string]interface{})
   597  			configReq.TracingConfig = &lambda.TracingConfig{
   598  				Mode: aws.String(config["mode"].(string)),
   599  			}
   600  			configUpdate = true
   601  		}
   602  	}
   603  	if d.HasChange("runtime") {
   604  		configReq.Runtime = aws.String(d.Get("runtime").(string))
   605  		configUpdate = true
   606  	}
   607  	if d.HasChange("environment") {
   608  		if v, ok := d.GetOk("environment"); ok {
   609  			environments := v.([]interface{})
   610  			environment, ok := environments[0].(map[string]interface{})
   611  			if !ok {
   612  				return errors.New("At least one field is expected inside environment")
   613  			}
   614  
   615  			if environmentVariables, ok := environment["variables"]; ok {
   616  				variables := readEnvironmentVariables(environmentVariables.(map[string]interface{}))
   617  
   618  				configReq.Environment = &lambda.Environment{
   619  					Variables: aws.StringMap(variables),
   620  				}
   621  				configUpdate = true
   622  			}
   623  		} else {
   624  			configReq.Environment = &lambda.Environment{
   625  				Variables: aws.StringMap(map[string]string{}),
   626  			}
   627  			configUpdate = true
   628  		}
   629  	}
   630  
   631  	if configUpdate {
   632  		log.Printf("[DEBUG] Send Update Lambda Function Configuration request: %#v", configReq)
   633  		_, err := conn.UpdateFunctionConfiguration(configReq)
   634  		if err != nil {
   635  			return fmt.Errorf("Error modifying Lambda Function Configuration %s: %s", d.Id(), err)
   636  		}
   637  		d.SetPartial("description")
   638  		d.SetPartial("handler")
   639  		d.SetPartial("memory_size")
   640  		d.SetPartial("role")
   641  		d.SetPartial("timeout")
   642  	}
   643  	d.Partial(false)
   644  
   645  	return resourceAwsLambdaFunctionRead(d, meta)
   646  }
   647  
   648  // loadFileContent returns contents of a file in a given path
   649  func loadFileContent(v string) ([]byte, error) {
   650  	filename, err := homedir.Expand(v)
   651  	if err != nil {
   652  		return nil, err
   653  	}
   654  	fileContent, err := ioutil.ReadFile(filename)
   655  	if err != nil {
   656  		return nil, err
   657  	}
   658  	return fileContent, nil
   659  }
   660  
   661  func readEnvironmentVariables(ev map[string]interface{}) map[string]string {
   662  	variables := make(map[string]string)
   663  	for k, v := range ev {
   664  		variables[k] = v.(string)
   665  	}
   666  
   667  	return variables
   668  }
   669  
   670  func validateVPCConfig(v interface{}) (map[string]interface{}, error) {
   671  	configs := v.([]interface{})
   672  	if len(configs) > 1 {
   673  		return nil, errors.New("Only a single vpc_config block is expected")
   674  	}
   675  
   676  	config, ok := configs[0].(map[string]interface{})
   677  
   678  	if !ok {
   679  		return nil, errors.New("vpc_config is <nil>")
   680  	}
   681  
   682  	// if subnet_ids and security_group_ids are both empty then the VPC is optional
   683  	if config["subnet_ids"].(*schema.Set).Len() == 0 && config["security_group_ids"].(*schema.Set).Len() == 0 {
   684  		return nil, nil
   685  	}
   686  
   687  	if config["subnet_ids"].(*schema.Set).Len() == 0 {
   688  		return nil, errors.New("vpc_config.subnet_ids cannot be empty")
   689  	}
   690  
   691  	if config["security_group_ids"].(*schema.Set).Len() == 0 {
   692  		return nil, errors.New("vpc_config.security_group_ids cannot be empty")
   693  	}
   694  
   695  	return config, nil
   696  }
   697  
   698  func validateRuntime(v interface{}, k string) (ws []string, errors []error) {
   699  	runtime := v.(string)
   700  
   701  	if runtime == lambda.RuntimeNodejs {
   702  		errors = append(errors, fmt.Errorf(
   703  			"%s has reached end of life since October 2016 and has been deprecated in favor of %s.",
   704  			runtime, lambda.RuntimeNodejs43))
   705  	}
   706  	return
   707  }