github.com/articulate/terraform@v0.6.13-0.20160303003731-8d31c93862de/builtin/providers/aws/resource_aws_lambda_function.go (about) 1 package aws 2 3 import ( 4 "crypto/sha256" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "time" 9 10 "github.com/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/aws/awserr" 12 "github.com/aws/aws-sdk-go/service/lambda" 13 "github.com/mitchellh/go-homedir" 14 15 "errors" 16 17 "github.com/hashicorp/terraform/helper/resource" 18 "github.com/hashicorp/terraform/helper/schema" 19 ) 20 21 func resourceAwsLambdaFunction() *schema.Resource { 22 return &schema.Resource{ 23 Create: resourceAwsLambdaFunctionCreate, 24 Read: resourceAwsLambdaFunctionRead, 25 Update: resourceAwsLambdaFunctionUpdate, 26 Delete: resourceAwsLambdaFunctionDelete, 27 28 Schema: map[string]*schema.Schema{ 29 "filename": &schema.Schema{ 30 Type: schema.TypeString, 31 Optional: true, 32 ConflictsWith: []string{"s3_bucket", "s3_key", "s3_object_version"}, 33 }, 34 "s3_bucket": &schema.Schema{ 35 Type: schema.TypeString, 36 Optional: true, 37 ConflictsWith: []string{"filename"}, 38 }, 39 "s3_key": &schema.Schema{ 40 Type: schema.TypeString, 41 Optional: true, 42 ConflictsWith: []string{"filename"}, 43 }, 44 "s3_object_version": &schema.Schema{ 45 Type: schema.TypeString, 46 Optional: true, 47 ConflictsWith: []string{"filename"}, 48 }, 49 "description": &schema.Schema{ 50 Type: schema.TypeString, 51 Optional: true, 52 }, 53 "function_name": &schema.Schema{ 54 Type: schema.TypeString, 55 Required: true, 56 ForceNew: true, 57 }, 58 "handler": &schema.Schema{ 59 Type: schema.TypeString, 60 Required: true, 61 ForceNew: true, // TODO make this editable 62 }, 63 "memory_size": &schema.Schema{ 64 Type: schema.TypeInt, 65 Optional: true, 66 Default: 128, 67 ForceNew: true, // TODO make this editable 68 }, 69 "role": &schema.Schema{ 70 Type: schema.TypeString, 71 Required: true, 72 ForceNew: true, // TODO make this editable 73 }, 74 "runtime": &schema.Schema{ 75 Type: schema.TypeString, 76 Optional: true, 77 ForceNew: true, 78 Default: "nodejs", 79 }, 80 "timeout": &schema.Schema{ 81 Type: schema.TypeInt, 82 Optional: true, 83 Default: 3, 84 ForceNew: true, // TODO make this editable 85 }, 86 "vpc_config": &schema.Schema{ 87 Type: schema.TypeList, 88 Optional: true, 89 ForceNew: true, 90 Elem: &schema.Resource{ 91 Schema: map[string]*schema.Schema{ 92 "subnet_ids": &schema.Schema{ 93 Type: schema.TypeSet, 94 Required: true, 95 ForceNew: true, 96 Elem: &schema.Schema{Type: schema.TypeString}, 97 Set: schema.HashString, 98 }, 99 "security_group_ids": &schema.Schema{ 100 Type: schema.TypeSet, 101 Required: true, 102 ForceNew: true, 103 Elem: &schema.Schema{Type: schema.TypeString}, 104 Set: schema.HashString, 105 }, 106 }, 107 }, 108 }, 109 "arn": &schema.Schema{ 110 Type: schema.TypeString, 111 Computed: true, 112 }, 113 "last_modified": &schema.Schema{ 114 Type: schema.TypeString, 115 Computed: true, 116 }, 117 "source_code_hash": &schema.Schema{ 118 Type: schema.TypeString, 119 Computed: true, 120 ForceNew: true, 121 }, 122 }, 123 } 124 } 125 126 // resourceAwsLambdaFunction maps to: 127 // CreateFunction in the API / SDK 128 func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) error { 129 conn := meta.(*AWSClient).lambdaconn 130 131 functionName := d.Get("function_name").(string) 132 iamRole := d.Get("role").(string) 133 134 log.Printf("[DEBUG] Creating Lambda Function %s with role %s", functionName, iamRole) 135 136 var functionCode *lambda.FunctionCode 137 if v, ok := d.GetOk("filename"); ok { 138 filename, err := homedir.Expand(v.(string)) 139 if err != nil { 140 return err 141 } 142 zipfile, err := ioutil.ReadFile(filename) 143 if err != nil { 144 return err 145 } 146 d.Set("source_code_hash", sha256.Sum256(zipfile)) 147 functionCode = &lambda.FunctionCode{ 148 ZipFile: zipfile, 149 } 150 } else { 151 s3Bucket, bucketOk := d.GetOk("s3_bucket") 152 s3Key, keyOk := d.GetOk("s3_key") 153 s3ObjectVersion, versionOk := d.GetOk("s3_object_version") 154 if !bucketOk || !keyOk { 155 return errors.New("s3_bucket and s3_key must all be set while using S3 code source") 156 } 157 functionCode = &lambda.FunctionCode{ 158 S3Bucket: aws.String(s3Bucket.(string)), 159 S3Key: aws.String(s3Key.(string)), 160 } 161 if versionOk { 162 functionCode.S3ObjectVersion = aws.String(s3ObjectVersion.(string)) 163 } 164 } 165 166 params := &lambda.CreateFunctionInput{ 167 Code: functionCode, 168 Description: aws.String(d.Get("description").(string)), 169 FunctionName: aws.String(functionName), 170 Handler: aws.String(d.Get("handler").(string)), 171 MemorySize: aws.Int64(int64(d.Get("memory_size").(int))), 172 Role: aws.String(iamRole), 173 Runtime: aws.String(d.Get("runtime").(string)), 174 Timeout: aws.Int64(int64(d.Get("timeout").(int))), 175 } 176 177 if v, ok := d.GetOk("vpc_config"); ok { 178 config, err := validateVPCConfig(v) 179 if err != nil { 180 return err 181 } 182 183 var subnetIds []*string 184 for _, id := range config["subnet_ids"].(*schema.Set).List() { 185 subnetIds = append(subnetIds, aws.String(id.(string))) 186 } 187 188 var securityGroupIds []*string 189 for _, id := range config["security_group_ids"].(*schema.Set).List() { 190 securityGroupIds = append(securityGroupIds, aws.String(id.(string))) 191 } 192 193 params.VpcConfig = &lambda.VpcConfig{ 194 SubnetIds: subnetIds, 195 SecurityGroupIds: securityGroupIds, 196 } 197 } 198 199 // IAM profiles can take ~10 seconds to propagate in AWS: 200 // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console 201 // Error creating Lambda function: InvalidParameterValueException: The role defined for the task cannot be assumed by Lambda. 202 err := resource.Retry(1*time.Minute, func() error { 203 _, err := conn.CreateFunction(params) 204 if err != nil { 205 if awserr, ok := err.(awserr.Error); ok { 206 if awserr.Code() == "InvalidParameterValueException" { 207 log.Printf("[DEBUG] InvalidParameterValueException creating Lambda Function: %s", awserr) 208 // Retryable 209 return awserr 210 } 211 } 212 log.Printf("[DEBUG] Error creating Lambda Function: %s", err) 213 // Not retryable 214 return resource.RetryError{Err: err} 215 } 216 // No error 217 return nil 218 }) 219 if err != nil { 220 return fmt.Errorf("Error creating Lambda function: %s", err) 221 } 222 223 d.SetId(d.Get("function_name").(string)) 224 225 return resourceAwsLambdaFunctionRead(d, meta) 226 } 227 228 // resourceAwsLambdaFunctionRead maps to: 229 // GetFunction in the API / SDK 230 func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) error { 231 conn := meta.(*AWSClient).lambdaconn 232 233 log.Printf("[DEBUG] Fetching Lambda Function: %s", d.Id()) 234 235 params := &lambda.GetFunctionInput{ 236 FunctionName: aws.String(d.Get("function_name").(string)), 237 } 238 239 getFunctionOutput, err := conn.GetFunction(params) 240 if err != nil { 241 return err 242 } 243 244 // getFunctionOutput.Code.Location is a pre-signed URL pointing at the zip 245 // file that we uploaded when we created the resource. You can use it to 246 // download the code from AWS. The other part is 247 // getFunctionOutput.Configuration which holds metadata. 248 249 function := getFunctionOutput.Configuration 250 // TODO error checking / handling on the Set() calls. 251 d.Set("arn", function.FunctionArn) 252 d.Set("description", function.Description) 253 d.Set("handler", function.Handler) 254 d.Set("memory_size", function.MemorySize) 255 d.Set("last_modified", function.LastModified) 256 d.Set("role", function.Role) 257 d.Set("runtime", function.Runtime) 258 d.Set("timeout", function.Timeout) 259 if config := flattenLambdaVpcConfigResponse(function.VpcConfig); len(config) > 0 { 260 d.Set("vpc_config", config) 261 } 262 263 return nil 264 } 265 266 // resourceAwsLambdaFunction maps to: 267 // DeleteFunction in the API / SDK 268 func resourceAwsLambdaFunctionDelete(d *schema.ResourceData, meta interface{}) error { 269 conn := meta.(*AWSClient).lambdaconn 270 271 log.Printf("[INFO] Deleting Lambda Function: %s", d.Id()) 272 273 params := &lambda.DeleteFunctionInput{ 274 FunctionName: aws.String(d.Get("function_name").(string)), 275 } 276 277 _, err := conn.DeleteFunction(params) 278 if err != nil { 279 return fmt.Errorf("Error deleting Lambda Function: %s", err) 280 } 281 282 d.SetId("") 283 284 return nil 285 } 286 287 // resourceAwsLambdaFunctionUpdate maps to: 288 // UpdateFunctionCode in the API / SDK 289 func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) error { 290 return nil 291 } 292 293 func validateVPCConfig(v interface{}) (map[string]interface{}, error) { 294 configs := v.([]interface{}) 295 if len(configs) > 1 { 296 return nil, errors.New("Only a single vpc_config block is expected") 297 } 298 299 config, ok := configs[0].(map[string]interface{}) 300 301 if !ok { 302 return nil, errors.New("vpc_config is <nil>") 303 } 304 305 if config["subnet_ids"].(*schema.Set).Len() == 0 { 306 return nil, errors.New("vpc_config.subnet_ids cannot be empty") 307 } 308 309 if config["security_group_ids"].(*schema.Set).Len() == 0 { 310 return nil, errors.New("vpc_config.security_group_ids cannot be empty") 311 } 312 313 return config, nil 314 }