github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/openstack/resource_openstack_compute_volume_attach_v2.go (about) 1 package openstack 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 "time" 8 9 "github.com/gophercloud/gophercloud" 10 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" 11 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 ) 15 16 func resourceComputeVolumeAttachV2() *schema.Resource { 17 return &schema.Resource{ 18 Create: resourceComputeVolumeAttachV2Create, 19 Read: resourceComputeVolumeAttachV2Read, 20 Delete: resourceComputeVolumeAttachV2Delete, 21 Importer: &schema.ResourceImporter{ 22 State: schema.ImportStatePassthrough, 23 }, 24 25 Schema: map[string]*schema.Schema{ 26 "region": &schema.Schema{ 27 Type: schema.TypeString, 28 Required: true, 29 ForceNew: true, 30 DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), 31 }, 32 33 "instance_id": &schema.Schema{ 34 Type: schema.TypeString, 35 Required: true, 36 ForceNew: true, 37 }, 38 39 "volume_id": &schema.Schema{ 40 Type: schema.TypeString, 41 Required: true, 42 ForceNew: true, 43 }, 44 45 "device": &schema.Schema{ 46 Type: schema.TypeString, 47 Computed: true, 48 Optional: true, 49 }, 50 }, 51 } 52 } 53 54 func resourceComputeVolumeAttachV2Create(d *schema.ResourceData, meta interface{}) error { 55 config := meta.(*Config) 56 computeClient, err := config.computeV2Client(GetRegion(d)) 57 if err != nil { 58 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 59 } 60 61 instanceId := d.Get("instance_id").(string) 62 volumeId := d.Get("volume_id").(string) 63 64 var device string 65 if v, ok := d.GetOk("device"); ok { 66 device = v.(string) 67 } 68 69 attachOpts := volumeattach.CreateOpts{ 70 Device: device, 71 VolumeID: volumeId, 72 } 73 74 log.Printf("[DEBUG] Creating volume attachment: %#v", attachOpts) 75 76 attachment, err := volumeattach.Create(computeClient, instanceId, attachOpts).Extract() 77 if err != nil { 78 return err 79 } 80 81 stateConf := &resource.StateChangeConf{ 82 Pending: []string{"ATTACHING"}, 83 Target: []string{"ATTACHED"}, 84 Refresh: resourceComputeVolumeAttachV2AttachFunc(computeClient, instanceId, attachment.ID), 85 Timeout: 10 * time.Minute, 86 Delay: 30 * time.Second, 87 MinTimeout: 15 * time.Second, 88 } 89 90 if _, err = stateConf.WaitForState(); err != nil { 91 return fmt.Errorf("Error attaching OpenStack volume: %s", err) 92 } 93 94 log.Printf("[DEBUG] Created volume attachment: %#v", attachment) 95 96 // Use the instance ID and attachment ID as the resource ID. 97 // This is because an attachment cannot be retrieved just by its ID alone. 98 id := fmt.Sprintf("%s/%s", instanceId, attachment.ID) 99 100 d.SetId(id) 101 102 return resourceComputeVolumeAttachV2Read(d, meta) 103 } 104 105 func resourceComputeVolumeAttachV2Read(d *schema.ResourceData, meta interface{}) error { 106 config := meta.(*Config) 107 computeClient, err := config.computeV2Client(GetRegion(d)) 108 if err != nil { 109 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 110 } 111 112 instanceId, attachmentId, err := parseComputeVolumeAttachmentId(d.Id()) 113 if err != nil { 114 return err 115 } 116 117 attachment, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract() 118 if err != nil { 119 return err 120 } 121 122 log.Printf("[DEBUG] Retrieved volume attachment: %#v", attachment) 123 124 d.Set("instance_id", attachment.ServerID) 125 d.Set("volume_id", attachment.VolumeID) 126 d.Set("device", attachment.Device) 127 d.Set("region", GetRegion(d)) 128 129 return nil 130 } 131 132 func resourceComputeVolumeAttachV2Delete(d *schema.ResourceData, meta interface{}) error { 133 config := meta.(*Config) 134 computeClient, err := config.computeV2Client(GetRegion(d)) 135 if err != nil { 136 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 137 } 138 139 instanceId, attachmentId, err := parseComputeVolumeAttachmentId(d.Id()) 140 if err != nil { 141 return err 142 } 143 144 stateConf := &resource.StateChangeConf{ 145 Pending: []string{""}, 146 Target: []string{"DETACHED"}, 147 Refresh: resourceComputeVolumeAttachV2DetachFunc(computeClient, instanceId, attachmentId), 148 Timeout: 10 * time.Minute, 149 Delay: 15 * time.Second, 150 MinTimeout: 15 * time.Second, 151 } 152 153 if _, err = stateConf.WaitForState(); err != nil { 154 return fmt.Errorf("Error detaching OpenStack volume: %s", err) 155 } 156 157 return nil 158 } 159 160 func resourceComputeVolumeAttachV2AttachFunc( 161 computeClient *gophercloud.ServiceClient, instanceId, attachmentId string) resource.StateRefreshFunc { 162 return func() (interface{}, string, error) { 163 va, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract() 164 if err != nil { 165 if _, ok := err.(gophercloud.ErrDefault404); ok { 166 return va, "ATTACHING", nil 167 } 168 return va, "", err 169 } 170 171 return va, "ATTACHED", nil 172 } 173 } 174 175 func resourceComputeVolumeAttachV2DetachFunc( 176 computeClient *gophercloud.ServiceClient, instanceId, attachmentId string) resource.StateRefreshFunc { 177 return func() (interface{}, string, error) { 178 log.Printf("[DEBUG] Attempting to detach OpenStack volume %s from instance %s", 179 attachmentId, instanceId) 180 181 va, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract() 182 if err != nil { 183 if _, ok := err.(gophercloud.ErrDefault404); ok { 184 return va, "DETACHED", nil 185 } 186 return va, "", err 187 } 188 189 err = volumeattach.Delete(computeClient, instanceId, attachmentId).ExtractErr() 190 if err != nil { 191 if _, ok := err.(gophercloud.ErrDefault404); ok { 192 return va, "DETACHED", nil 193 } 194 195 if _, ok := err.(gophercloud.ErrDefault400); ok { 196 return nil, "", nil 197 } 198 199 return nil, "", err 200 } 201 202 log.Printf("[DEBUG] OpenStack Volume Attachment (%s) is still active.", attachmentId) 203 return nil, "", nil 204 } 205 } 206 207 func parseComputeVolumeAttachmentId(id string) (string, string, error) { 208 idParts := strings.Split(id, "/") 209 if len(idParts) < 2 { 210 return "", "", fmt.Errorf("Unable to determine volume attachment ID") 211 } 212 213 instanceId := idParts[0] 214 attachmentId := idParts[1] 215 216 return instanceId, attachmentId, nil 217 }