github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/builtin/providers/google/resource_compute_disk.go (about) 1 package google 2 3 import ( 4 "fmt" 5 "log" 6 "regexp" 7 "strings" 8 9 "github.com/hashicorp/terraform/helper/schema" 10 "google.golang.org/api/compute/v1" 11 "google.golang.org/api/googleapi" 12 ) 13 14 const ( 15 computeDiskUserRegexString = "^(?:https://www.googleapis.com/compute/v1/projects/)?([-_a-zA-Z0-9]*)/zones/([-_a-zA-Z0-9]*)/instances/([-_a-zA-Z0-9]*)$" 16 ) 17 18 var ( 19 computeDiskUserRegex = regexp.MustCompile(computeDiskUserRegexString) 20 ) 21 22 func resourceComputeDisk() *schema.Resource { 23 return &schema.Resource{ 24 Create: resourceComputeDiskCreate, 25 Read: resourceComputeDiskRead, 26 Update: resourceComputeDiskUpdate, 27 Delete: resourceComputeDiskDelete, 28 Importer: &schema.ResourceImporter{ 29 State: schema.ImportStatePassthrough, 30 }, 31 32 Schema: map[string]*schema.Schema{ 33 "name": &schema.Schema{ 34 Type: schema.TypeString, 35 Required: true, 36 ForceNew: true, 37 }, 38 39 "zone": &schema.Schema{ 40 Type: schema.TypeString, 41 Required: true, 42 ForceNew: true, 43 }, 44 45 "disk_encryption_key_raw": &schema.Schema{ 46 Type: schema.TypeString, 47 Optional: true, 48 ForceNew: true, 49 Sensitive: true, 50 }, 51 52 "disk_encryption_key_sha256": &schema.Schema{ 53 Type: schema.TypeString, 54 Computed: true, 55 }, 56 57 "image": &schema.Schema{ 58 Type: schema.TypeString, 59 Optional: true, 60 ForceNew: true, 61 }, 62 63 "project": &schema.Schema{ 64 Type: schema.TypeString, 65 Optional: true, 66 ForceNew: true, 67 }, 68 69 "size": &schema.Schema{ 70 Type: schema.TypeInt, 71 Optional: true, 72 }, 73 74 "self_link": &schema.Schema{ 75 Type: schema.TypeString, 76 Computed: true, 77 }, 78 79 "snapshot": &schema.Schema{ 80 Type: schema.TypeString, 81 Optional: true, 82 ForceNew: true, 83 }, 84 85 "type": &schema.Schema{ 86 Type: schema.TypeString, 87 Optional: true, 88 ForceNew: true, 89 }, 90 "users": &schema.Schema{ 91 Type: schema.TypeList, 92 Computed: true, 93 Elem: &schema.Schema{Type: schema.TypeString}, 94 }, 95 }, 96 } 97 } 98 99 func resourceComputeDiskCreate(d *schema.ResourceData, meta interface{}) error { 100 config := meta.(*Config) 101 102 project, err := getProject(d, config) 103 if err != nil { 104 return err 105 } 106 107 // Get the zone 108 log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string)) 109 zone, err := config.clientCompute.Zones.Get( 110 project, d.Get("zone").(string)).Do() 111 if err != nil { 112 return fmt.Errorf( 113 "Error loading zone '%s': %s", d.Get("zone").(string), err) 114 } 115 116 // Build the disk parameter 117 disk := &compute.Disk{ 118 Name: d.Get("name").(string), 119 SizeGb: int64(d.Get("size").(int)), 120 } 121 122 // If we were given a source image, load that. 123 if v, ok := d.GetOk("image"); ok { 124 log.Printf("[DEBUG] Resolving image name: %s", v.(string)) 125 imageUrl, err := resolveImage(config, v.(string)) 126 if err != nil { 127 return fmt.Errorf( 128 "Error resolving image name '%s': %s", 129 v.(string), err) 130 } 131 132 disk.SourceImage = imageUrl 133 log.Printf("[DEBUG] Image name resolved to: %s", imageUrl) 134 } 135 136 if v, ok := d.GetOk("type"); ok { 137 log.Printf("[DEBUG] Loading disk type: %s", v.(string)) 138 diskType, err := readDiskType(config, zone, v.(string)) 139 if err != nil { 140 return fmt.Errorf( 141 "Error loading disk type '%s': %s", 142 v.(string), err) 143 } 144 145 disk.Type = diskType.SelfLink 146 } 147 148 if v, ok := d.GetOk("snapshot"); ok { 149 snapshotName := v.(string) 150 match, _ := regexp.MatchString("^https://www.googleapis.com/compute", snapshotName) 151 if match { 152 disk.SourceSnapshot = snapshotName 153 } else { 154 log.Printf("[DEBUG] Loading snapshot: %s", snapshotName) 155 snapshotData, err := config.clientCompute.Snapshots.Get( 156 project, snapshotName).Do() 157 158 if err != nil { 159 return fmt.Errorf( 160 "Error loading snapshot '%s': %s", 161 snapshotName, err) 162 } 163 disk.SourceSnapshot = snapshotData.SelfLink 164 } 165 } 166 167 if v, ok := d.GetOk("disk_encryption_key_raw"); ok { 168 disk.DiskEncryptionKey = &compute.CustomerEncryptionKey{} 169 disk.DiskEncryptionKey.RawKey = v.(string) 170 } 171 172 op, err := config.clientCompute.Disks.Insert( 173 project, d.Get("zone").(string), disk).Do() 174 if err != nil { 175 return fmt.Errorf("Error creating disk: %s", err) 176 } 177 178 // It probably maybe worked, so store the ID now 179 d.SetId(disk.Name) 180 181 err = computeOperationWaitZone(config, op, project, d.Get("zone").(string), "Creating Disk") 182 if err != nil { 183 return err 184 } 185 return resourceComputeDiskRead(d, meta) 186 } 187 188 func resourceComputeDiskUpdate(d *schema.ResourceData, meta interface{}) error { 189 config := meta.(*Config) 190 191 project, err := getProject(d, config) 192 if err != nil { 193 return err 194 } 195 196 if d.HasChange("size") { 197 rb := &compute.DisksResizeRequest{ 198 SizeGb: int64(d.Get("size").(int)), 199 } 200 _, err := config.clientCompute.Disks.Resize( 201 project, d.Get("zone").(string), d.Id(), rb).Do() 202 if err != nil { 203 return fmt.Errorf("Error resizing disk: %s", err) 204 } 205 } 206 207 return resourceComputeDiskRead(d, meta) 208 } 209 210 func resourceComputeDiskRead(d *schema.ResourceData, meta interface{}) error { 211 config := meta.(*Config) 212 213 project, err := getProject(d, config) 214 if err != nil { 215 return err 216 } 217 218 region, err := getRegion(d, config) 219 if err != nil { 220 return err 221 } 222 223 getDisk := func(zone string) (interface{}, error) { 224 return config.clientCompute.Disks.Get(project, zone, d.Id()).Do() 225 } 226 227 var disk *compute.Disk 228 if zone, ok := d.GetOk("zone"); ok { 229 disk, err = config.clientCompute.Disks.Get( 230 project, zone.(string), d.Id()).Do() 231 if err != nil { 232 return handleNotFoundError(err, d, fmt.Sprintf("Disk %q", d.Get("name").(string))) 233 } 234 } else { 235 // If the resource was imported, the only info we have is the ID. Try to find the resource 236 // by searching in the region of the project. 237 var resource interface{} 238 resource, err = getZonalResourceFromRegion(getDisk, region, config.clientCompute, project) 239 240 if err != nil { 241 return err 242 } 243 244 disk = resource.(*compute.Disk) 245 } 246 247 zoneUrlParts := strings.Split(disk.Zone, "/") 248 typeUrlParts := strings.Split(disk.Type, "/") 249 d.Set("name", disk.Name) 250 d.Set("self_link", disk.SelfLink) 251 d.Set("type", typeUrlParts[len(typeUrlParts)-1]) 252 d.Set("zone", zoneUrlParts[len(zoneUrlParts)-1]) 253 d.Set("size", disk.SizeGb) 254 d.Set("users", disk.Users) 255 if disk.DiskEncryptionKey != nil && disk.DiskEncryptionKey.Sha256 != "" { 256 d.Set("disk_encryption_key_sha256", disk.DiskEncryptionKey.Sha256) 257 } 258 if disk.SourceImage != "" { 259 imageUrlParts := strings.Split(disk.SourceImage, "/") 260 d.Set("image", imageUrlParts[len(imageUrlParts)-1]) 261 } 262 d.Set("snapshot", disk.SourceSnapshot) 263 264 return nil 265 } 266 267 func resourceComputeDiskDelete(d *schema.ResourceData, meta interface{}) error { 268 config := meta.(*Config) 269 270 project, err := getProject(d, config) 271 if err != nil { 272 return err 273 } 274 275 // if disks are attached, they must be detached before the disk can be deleted 276 if instances, ok := d.Get("users").([]interface{}); ok { 277 type detachArgs struct{ project, zone, instance, deviceName string } 278 var detachCalls []detachArgs 279 self := d.Get("self_link").(string) 280 for _, instance := range instances { 281 if !computeDiskUserRegex.MatchString(instance.(string)) { 282 return fmt.Errorf("Unknown user %q of disk %q", instance, self) 283 } 284 matches := computeDiskUserRegex.FindStringSubmatch(instance.(string)) 285 instanceProject := matches[1] 286 instanceZone := matches[2] 287 instanceName := matches[3] 288 i, err := config.clientCompute.Instances.Get(instanceProject, instanceZone, instanceName).Do() 289 if err != nil { 290 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 291 log.Printf("[WARN] instance %q not found, not bothering to detach disks", instance.(string)) 292 continue 293 } 294 return fmt.Errorf("Error retrieving instance %s: %s", instance.(string), err.Error()) 295 } 296 for _, disk := range i.Disks { 297 if disk.Source == self { 298 detachCalls = append(detachCalls, detachArgs{ 299 project: project, 300 zone: i.Zone, 301 instance: i.Name, 302 deviceName: disk.DeviceName, 303 }) 304 } 305 } 306 } 307 for _, call := range detachCalls { 308 op, err := config.clientCompute.Instances.DetachDisk(call.project, call.zone, call.instance, call.deviceName).Do() 309 if err != nil { 310 return fmt.Errorf("Error detaching disk %s from instance %s/%s/%s: %s", call.deviceName, call.project, 311 call.zone, call.instance, err.Error()) 312 } 313 err = computeOperationWaitZone(config, op, call.project, call.zone, 314 fmt.Sprintf("Detaching disk from %s/%s/%s", call.project, call.zone, call.instance)) 315 if err != nil { 316 return err 317 } 318 } 319 } 320 321 // Delete the disk 322 op, err := config.clientCompute.Disks.Delete( 323 project, d.Get("zone").(string), d.Id()).Do() 324 if err != nil { 325 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 326 log.Printf("[WARN] Removing Disk %q because it's gone", d.Get("name").(string)) 327 // The resource doesn't exist anymore 328 d.SetId("") 329 return nil 330 } 331 return fmt.Errorf("Error deleting disk: %s", err) 332 } 333 334 zone := d.Get("zone").(string) 335 err = computeOperationWaitZone(config, op, project, zone, "Deleting Disk") 336 if err != nil { 337 return err 338 } 339 340 d.SetId("") 341 return nil 342 }