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