github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_efs_mount_target.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "time" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/aws/awserr" 10 "github.com/aws/aws-sdk-go/service/ec2" 11 "github.com/aws/aws-sdk-go/service/efs" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 ) 15 16 func resourceAwsEfsMountTarget() *schema.Resource { 17 return &schema.Resource{ 18 Create: resourceAwsEfsMountTargetCreate, 19 Read: resourceAwsEfsMountTargetRead, 20 Update: resourceAwsEfsMountTargetUpdate, 21 Delete: resourceAwsEfsMountTargetDelete, 22 23 Importer: &schema.ResourceImporter{ 24 State: schema.ImportStatePassthrough, 25 }, 26 27 Schema: map[string]*schema.Schema{ 28 "file_system_id": &schema.Schema{ 29 Type: schema.TypeString, 30 Required: true, 31 ForceNew: true, 32 }, 33 34 "ip_address": &schema.Schema{ 35 Type: schema.TypeString, 36 Computed: true, 37 Optional: true, 38 ForceNew: true, 39 }, 40 41 "security_groups": &schema.Schema{ 42 Type: schema.TypeSet, 43 Elem: &schema.Schema{Type: schema.TypeString}, 44 Set: schema.HashString, 45 Computed: true, 46 Optional: true, 47 }, 48 49 "subnet_id": &schema.Schema{ 50 Type: schema.TypeString, 51 Required: true, 52 ForceNew: true, 53 }, 54 55 "network_interface_id": &schema.Schema{ 56 Type: schema.TypeString, 57 Computed: true, 58 }, 59 "dns_name": &schema.Schema{ 60 Type: schema.TypeString, 61 Computed: true, 62 }, 63 }, 64 } 65 } 66 67 func resourceAwsEfsMountTargetCreate(d *schema.ResourceData, meta interface{}) error { 68 conn := meta.(*AWSClient).efsconn 69 70 fsId := d.Get("file_system_id").(string) 71 subnetId := d.Get("subnet_id").(string) 72 73 // CreateMountTarget would return the same Mount Target ID 74 // to parallel requests if they both include the same AZ 75 // and we would end up managing the same MT as 2 resources. 76 // So we make it fail by calling 1 request per AZ at a time. 77 az, err := getAzFromSubnetId(subnetId, meta.(*AWSClient).ec2conn) 78 if err != nil { 79 return fmt.Errorf("Failed getting Availability Zone from subnet ID (%s): %s", subnetId, err) 80 } 81 mtKey := "efs-mt-" + fsId + "-" + az 82 awsMutexKV.Lock(mtKey) 83 defer awsMutexKV.Unlock(mtKey) 84 85 input := efs.CreateMountTargetInput{ 86 FileSystemId: aws.String(fsId), 87 SubnetId: aws.String(subnetId), 88 } 89 90 if v, ok := d.GetOk("ip_address"); ok { 91 input.IpAddress = aws.String(v.(string)) 92 } 93 if v, ok := d.GetOk("security_groups"); ok { 94 input.SecurityGroups = expandStringList(v.(*schema.Set).List()) 95 } 96 97 log.Printf("[DEBUG] Creating EFS mount target: %#v", input) 98 99 mt, err := conn.CreateMountTarget(&input) 100 if err != nil { 101 return err 102 } 103 104 d.SetId(*mt.MountTargetId) 105 log.Printf("[INFO] EFS mount target ID: %s", d.Id()) 106 107 stateConf := &resource.StateChangeConf{ 108 Pending: []string{"creating"}, 109 Target: []string{"available"}, 110 Refresh: func() (interface{}, string, error) { 111 resp, err := conn.DescribeMountTargets(&efs.DescribeMountTargetsInput{ 112 MountTargetId: aws.String(d.Id()), 113 }) 114 if err != nil { 115 return nil, "error", err 116 } 117 118 if hasEmptyMountTargets(resp) { 119 return nil, "error", fmt.Errorf("EFS mount target %q could not be found.", d.Id()) 120 } 121 122 mt := resp.MountTargets[0] 123 124 log.Printf("[DEBUG] Current status of %q: %q", *mt.MountTargetId, *mt.LifeCycleState) 125 return mt, *mt.LifeCycleState, nil 126 }, 127 Timeout: 10 * time.Minute, 128 Delay: 2 * time.Second, 129 MinTimeout: 3 * time.Second, 130 } 131 132 _, err = stateConf.WaitForState() 133 if err != nil { 134 return fmt.Errorf("Error waiting for EFS mount target (%s) to create: %s", d.Id(), err) 135 } 136 137 log.Printf("[DEBUG] EFS mount target created: %s", *mt.MountTargetId) 138 139 return resourceAwsEfsMountTargetRead(d, meta) 140 } 141 142 func resourceAwsEfsMountTargetUpdate(d *schema.ResourceData, meta interface{}) error { 143 conn := meta.(*AWSClient).efsconn 144 145 if d.HasChange("security_groups") { 146 input := efs.ModifyMountTargetSecurityGroupsInput{ 147 MountTargetId: aws.String(d.Id()), 148 SecurityGroups: expandStringList(d.Get("security_groups").(*schema.Set).List()), 149 } 150 _, err := conn.ModifyMountTargetSecurityGroups(&input) 151 if err != nil { 152 return err 153 } 154 } 155 156 return resourceAwsEfsMountTargetRead(d, meta) 157 } 158 159 func resourceAwsEfsMountTargetRead(d *schema.ResourceData, meta interface{}) error { 160 conn := meta.(*AWSClient).efsconn 161 resp, err := conn.DescribeMountTargets(&efs.DescribeMountTargetsInput{ 162 MountTargetId: aws.String(d.Id()), 163 }) 164 if err != nil { 165 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "MountTargetNotFound" { 166 // The EFS mount target could not be found, 167 // which would indicate that it might be 168 // already deleted. 169 log.Printf("[WARN] EFS mount target %q could not be found.", d.Id()) 170 d.SetId("") 171 return nil 172 } 173 return fmt.Errorf("Error reading EFS mount target %s: %s", d.Id(), err) 174 } 175 176 if hasEmptyMountTargets(resp) { 177 return fmt.Errorf("EFS mount target %q could not be found.", d.Id()) 178 } 179 180 mt := resp.MountTargets[0] 181 182 log.Printf("[DEBUG] Found EFS mount target: %#v", mt) 183 184 d.SetId(*mt.MountTargetId) 185 d.Set("file_system_id", mt.FileSystemId) 186 d.Set("ip_address", mt.IpAddress) 187 d.Set("subnet_id", mt.SubnetId) 188 d.Set("network_interface_id", mt.NetworkInterfaceId) 189 190 sgResp, err := conn.DescribeMountTargetSecurityGroups(&efs.DescribeMountTargetSecurityGroupsInput{ 191 MountTargetId: aws.String(d.Id()), 192 }) 193 if err != nil { 194 return err 195 } 196 197 err = d.Set("security_groups", schema.NewSet(schema.HashString, flattenStringList(sgResp.SecurityGroups))) 198 if err != nil { 199 return err 200 } 201 202 // DNS name per http://docs.aws.amazon.com/efs/latest/ug/mounting-fs-mount-cmd-dns-name.html 203 _, err = getAzFromSubnetId(*mt.SubnetId, meta.(*AWSClient).ec2conn) 204 if err != nil { 205 return fmt.Errorf("Failed getting Availability Zone from subnet ID (%s): %s", *mt.SubnetId, err) 206 } 207 208 region := meta.(*AWSClient).region 209 err = d.Set("dns_name", resourceAwsEfsMountTargetDnsName(*mt.FileSystemId, region)) 210 if err != nil { 211 return err 212 } 213 214 return nil 215 } 216 217 func getAzFromSubnetId(subnetId string, conn *ec2.EC2) (string, error) { 218 input := ec2.DescribeSubnetsInput{ 219 SubnetIds: []*string{aws.String(subnetId)}, 220 } 221 out, err := conn.DescribeSubnets(&input) 222 if err != nil { 223 return "", err 224 } 225 226 if l := len(out.Subnets); l != 1 { 227 return "", fmt.Errorf("Expected exactly 1 subnet returned for %q, got: %d", subnetId, l) 228 } 229 230 return *out.Subnets[0].AvailabilityZone, nil 231 } 232 233 func resourceAwsEfsMountTargetDelete(d *schema.ResourceData, meta interface{}) error { 234 conn := meta.(*AWSClient).efsconn 235 236 log.Printf("[DEBUG] Deleting EFS mount target %q", d.Id()) 237 _, err := conn.DeleteMountTarget(&efs.DeleteMountTargetInput{ 238 MountTargetId: aws.String(d.Id()), 239 }) 240 if err != nil { 241 return err 242 } 243 244 stateConf := &resource.StateChangeConf{ 245 Pending: []string{"available", "deleting", "deleted"}, 246 Target: []string{}, 247 Refresh: func() (interface{}, string, error) { 248 resp, err := conn.DescribeMountTargets(&efs.DescribeMountTargetsInput{ 249 MountTargetId: aws.String(d.Id()), 250 }) 251 if err != nil { 252 awsErr, ok := err.(awserr.Error) 253 if !ok { 254 return nil, "error", err 255 } 256 257 if awsErr.Code() == "MountTargetNotFound" { 258 return nil, "", nil 259 } 260 261 return nil, "error", awsErr 262 } 263 264 if hasEmptyMountTargets(resp) { 265 return nil, "", nil 266 } 267 268 mt := resp.MountTargets[0] 269 270 log.Printf("[DEBUG] Current status of %q: %q", *mt.MountTargetId, *mt.LifeCycleState) 271 return mt, *mt.LifeCycleState, nil 272 }, 273 Timeout: 10 * time.Minute, 274 Delay: 2 * time.Second, 275 MinTimeout: 3 * time.Second, 276 } 277 278 _, err = stateConf.WaitForState() 279 if err != nil { 280 return fmt.Errorf("Error waiting for EFS mount target (%q) to delete: %s", 281 d.Id(), err.Error()) 282 } 283 284 log.Printf("[DEBUG] EFS mount target %q deleted.", d.Id()) 285 286 return nil 287 } 288 289 func resourceAwsEfsMountTargetDnsName(fileSystemId, region string) string { 290 return fmt.Sprintf("%s.efs.%s.amazonaws.com", fileSystemId, region) 291 } 292 293 func hasEmptyMountTargets(mto *efs.DescribeMountTargetsOutput) bool { 294 if mto != nil && len(mto.MountTargets) > 0 { 295 return false 296 } 297 return true 298 }