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