func DiskDiffOperation(d *schema.ResourceDiff, c *govmomi.Client) error {
	log.Printf("[DEBUG] DiskDiffOperation: Beginning disk diff customization")
	o, n := d.GetChange(subresourceTypeDisk)
	// Some global validation first. We handle individual validation later.
	log.Printf("[DEBUG] DiskDiffOperation: Beginning collective diff validation (indexes aligned to new config)")
	names := make(map[string]struct{})
	attachments := make(map[string]struct{})
	units := make(map[int]struct{})
	if len(n.([]interface{})) < 1 {
		return errors.New("there must be at least one disk specified")
	}
	for ni, ne := range n.([]interface{}) {
		nm := ne.(map[string]interface{})
		name, err := diskLabelOrName(nm)
		if err != nil {
			return fmt.Errorf("disk.%d: %s", ni, err)
		}
		if _, ok := names[name]; ok {
			return fmt.Errorf("disk: duplicate name %s", name)
		}
		// If attach is set, we need to validate that there's no other duplicate paths.
		if nm["attach"].(bool) {
			path := diskPathOrName(nm)
			if path == "" {
				return fmt.Errorf("disk.%d: path or name cannot be empty when using attach", ni)
			}
			if _, ok := attachments[path]; ok {
				return fmt.Errorf("disk: multiple entries trying to attach external disk %s", path)
			}
			attachments[path] = struct{}{}
		}

		if _, ok := units[nm["unit_number"].(int)]; ok {
			return fmt.Errorf("disk: duplicate unit_number %d", nm["unit_number"].(int))
		}
		names[name] = struct{}{}
		units[nm["unit_number"].(int)] = struct{}{}
		r := NewDiskSubresource(c, d, nm, nil, ni)
		if err := r.DiffGeneral(); err != nil {
			return fmt.Errorf("%s: %s", r.Addr(), err)
		}
	}
	if _, ok := units[0]; !ok {
		return errors.New("at least one disk must have a unit_number of 0")
	}

	// Perform the normalization here.
	log.Printf("[DEBUG] DiskDiffOperation: Beginning diff validation and normalization (indexes aligned to old state)")
	ods := o.([]interface{})
	nds := n.([]interface{})

	normalized := make([]interface{}, len(ods))
nextNew:
	for _, ne := range nds {
		nm := ne.(map[string]interface{})
		for oi, oe := range ods {
			om := oe.(map[string]interface{})
			var oname, nname string
			var err error
			if oname, err = diskLabelOrName(om); err != nil {
				return fmt.Errorf("disk.%d: %s", oi, err)
			}
			if nname, err = diskLabelOrName(nm); err != nil {
				return fmt.Errorf("disk.%d: %s", oi, err)
			}
			// We extrapolate using the label as a "primary key" of sorts.
			if nname == oname {
				r := NewDiskSubresource(c, d, nm, om, oi)
				if err := r.DiffExisting(); err != nil {
					return fmt.Errorf("%s: %s", r.Addr(), err)
				}
				normalized[oi] = r.Data()
				continue nextNew
			}
		}
		// We didn't find a match for this resource, it could be a new resource or
		// significantly altered. Put it back on the list in the same form we got
		// it in, but all computed data first, just in case it was in a position
		// previously occupied by an existing resource.
		nm["key"] = 0
		nm["device_address"] = ""
		nm["uuid"] = ""
		if a, ok := nm["attach"]; !ok || !a.(bool) {
			nm["path"] = ""
		} else {
			_, ok := nm["size"]
			if ok {
				delete(nm, "size")
			}
		}
		if dsID, ok := nm["datastore_id"]; !ok || dsID == "" {
			nm["datastore_id"] = diskDatastoreComputedName
		}
		normalized = append(normalized, nm)
	}

	// Go thru the new list, and replace any nils with a "deleted" copy of the
	// old resource. This is basically a copy of the old entry with <deleted> in
	// place of the label, so it shows up nicely in the diff.
	for ni, ne := range normalized {
		if ne != nil {
			continue
		}
		nv, err := copystructure.Copy(ods[ni])
		if err != nil {
			return fmt.Errorf("disk.%d: error making updated diff of deleted entry: %s", ni, err)
		}
		nm := nv.(map[string]interface{})
		// Clear out the name. We put the message in label now, even if name was
		// the item defined.  TODO: Remove this after 2.0.
		nm["name"] = ""
		switch {
		case nm["keep_on_remove"].(bool):
			fallthrough
		case nm["attach"].(bool):
			nm["label"] = diskDetachedName
		default:
			nm["label"] = diskDeletedName
		}
		normalized[ni] = nm
	}

	// All done. We can end the customization off by setting the new, normalized diff.
	log.Printf("[DEBUG] DiskDiffOperation: New resource set post-normalization: %s", subresourceListString(normalized))
	log.Printf("[DEBUG] DiskDiffOperation: Disk diff customization complete, sending new diff")
	return d.SetNew(subresourceTypeDisk, normalized)
}
func DiskDiffOperation(d *schema.ResourceDiff, c *govmomi.Client) error {
	log.Printf("[DEBUG] DiskDiffOperation: Beginning disk diff customization")
	o, n := d.GetChange(subresourceTypeDisk)
	// Some global validation first. We handle individual validation later.
	log.Printf("[DEBUG] DiskDiffOperation: Beginning collective diff validation (indexes aligned to new config)")
	names := make(map[string]struct{})
	attachments := make(map[string]struct{})
	units := make(map[int]struct{})
	if len(n.([]interface{})) < 1 {
		return errors.New("there must be at least one disk specified")
	}
	for ni, ne := range n.([]interface{}) {
		nm := ne.(map[string]interface{})
		name, err := diskLabelOrName(nm)
		if err != nil {
			return fmt.Errorf("disk.%d: %s", ni, err)
		}
		if _, ok := names[name]; ok {
			return fmt.Errorf("disk: duplicate name %s", name)
		}
		// If attach is set, we need to validate that there's no other duplicate paths.
		if nm["attach"].(bool) {
			path := diskPathOrName(nm)
			if path == "" {
				return fmt.Errorf("disk.%d: path or name cannot be empty when using attach", ni)
			}
			if _, ok := attachments[path]; ok {
				return fmt.Errorf("disk: multiple entries trying to attach external disk %s", path)
			}
			attachments[path] = struct{}{}
		}

		if _, ok := units[nm["unit_number"].(int)]; ok {
			return fmt.Errorf("disk: duplicate unit_number %d", nm["unit_number"].(int))
		}
		names[name] = struct{}{}
		units[nm["unit_number"].(int)] = struct{}{}
		r := NewDiskSubresource(c, d, nm, nil, ni)
		if err := r.DiffGeneral(); err != nil {
			return fmt.Errorf("%s: %s", r.Addr(), err)
		}
	}
	if _, ok := units[0]; !ok {
		return errors.New("at least one disk must have a unit_number of 0")
	}

	// Perform the normalization here.
	log.Printf("[DEBUG] DiskDiffOperation: Beginning diff validation and normalization (indexes aligned to old state)")
	ods := o.([]interface{})
	nds := n.([]interface{})

	normalized := make([]interface{}, len(ods))
nextNew:
	for _, ne := range nds {
		nm := ne.(map[string]interface{})
		for oi, oe := range ods {
			om := oe.(map[string]interface{})
			var oname, nname string
			var err error
			if oname, err = diskLabelOrName(om); err != nil {
				return fmt.Errorf("disk.%d: %s", oi, err)
			}
			if nname, err = diskLabelOrName(nm); err != nil {
				return fmt.Errorf("disk.%d: %s", oi, err)
			}
			// We extrapolate using the label as a "primary key" of sorts.
			if nname == oname {
				r := NewDiskSubresource(c, d, nm, om, oi)
				if err := r.DiffExisting(); err != nil {
					return fmt.Errorf("%s: %s", r.Addr(), err)
				}
				normalized[oi] = r.Data()
				continue nextNew
			}
		}
		// We didn't find a match for this resource, it could be a new resource or
		// significantly altered. Put it back on the list in the same form we got
		// it in, but all computed data first, just in case it was in a position
		// previously occupied by an existing resource.
		nm["key"] = 0
		nm["device_address"] = ""
		nm["uuid"] = ""
		if a, ok := nm["attach"]; !ok || !a.(bool) {
			nm["path"] = ""
		} else {
			_, ok := nm["size"]
			if ok {
				delete(nm, "size")
			}
		}
		if dsID, ok := nm["datastore_id"]; !ok || dsID == "" {
			nm["datastore_id"] = diskDatastoreComputedName
		}
		normalized = append(normalized, nm)
	}

	// Go thru the new list, and replace any nils with a "deleted" copy of the
	// old resource. This is basically a copy of the old entry with <deleted> in
	// place of the label, so it shows up nicely in the diff.
	for ni, ne := range normalized {
		if ne != nil {
			continue
		}
		nv, err := copystructure.Copy(ods[ni])
		if err != nil {
			return fmt.Errorf("disk.%d: error making updated diff of deleted entry: %s", ni, err)
		}
		nm := nv.(map[string]interface{})
		// Clear out the name. We put the message in label now, even if name was
		// the item defined.  TODO: Remove this after 2.0.
		nm["name"] = ""
		switch {
		case nm["keep_on_remove"].(bool):
			fallthrough
		case nm["attach"].(bool):
			nm["label"] = diskDetachedName
		default:
			nm["label"] = diskDeletedName
		}
		normalized[ni] = nm
	}

	// All done. We can end the customization off by setting the new, normalized diff.
	log.Printf("[DEBUG] DiskDiffOperation: New resource set post-normalization: %s", subresourceListString(normalized))
	log.Printf("[DEBUG] DiskDiffOperation: Disk diff customization complete, sending new diff")
	return d.SetNew(subresourceTypeDisk, normalized)
}
// DiskDiffOperation performs operations relevant to managing the diff on disk
// sub-resources.
//
// Most importantly, this works to prevent spurious diffs by extrapolating the
// correct correlation between the old and new sets using the name as a primary
// key, and then normalizing the two diffs so that computed data is properly
// set.
//
// The following validation operations are also carried out on the set as a
// whole:
//
// * Ensuring all names are unique across the set.
// * Ensuring that at least one element in the set has a unit_number of 0.