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  }