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