mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-10-01 16:21:06 +00:00
Add boot_disk
property to google_compute_instance
(#122)
* Add boot_disk property to google_compute_instance * docs for boot_disk * limit scope of bootDisk, use bool instead * test formatting * make device_name forcenew, add sha256 encryption key
This commit is contained in:
parent
2f17ba7596
commit
549e1314f9
@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/helper/validation"
|
||||
"google.golang.org/api/compute/v1"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
@ -26,6 +27,86 @@ func resourceComputeInstance() *schema.Resource {
|
||||
MigrateState: resourceComputeInstanceMigrateState,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"boot_disk": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"auto_delete": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"device_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"disk_encryption_key_raw": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
|
||||
"disk_encryption_key_sha256": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"initialize_params": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"size": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
||||
if v.(int) < 1 {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"%q must be greater than 0", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
|
||||
"type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validation.StringInSlice([]string{"pd-standard", "pd-ssd"}, false),
|
||||
},
|
||||
|
||||
"image": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"source": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
ConflictsWith: []string{"boot_disk.initialize_params"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"disk": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
@ -407,12 +488,23 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
|
||||
}
|
||||
|
||||
// Build up the list of disks
|
||||
|
||||
disks := []*compute.AttachedDisk{}
|
||||
var hasBootDisk bool
|
||||
if _, hasBootDisk = d.GetOk("boot_disk"); hasBootDisk {
|
||||
bootDisk, err := expandBootDisk(d, config, zone, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
disks = append(disks, bootDisk)
|
||||
}
|
||||
|
||||
disksCount := d.Get("disk.#").(int)
|
||||
attachedDisksCount := d.Get("attached_disk.#").(int)
|
||||
if disksCount+attachedDisksCount == 0 {
|
||||
return fmt.Errorf("At least one disk or attached_disk must be set")
|
||||
|
||||
if disksCount+attachedDisksCount == 0 && !hasBootDisk {
|
||||
return fmt.Errorf("At least one disk, attached_disk, or boot_disk must be set")
|
||||
}
|
||||
disks := make([]*compute.AttachedDisk, 0, disksCount+attachedDisksCount)
|
||||
for i := 0; i < disksCount; i++ {
|
||||
prefix := fmt.Sprintf("disk.%d", i)
|
||||
|
||||
@ -422,7 +514,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
|
||||
var disk compute.AttachedDisk
|
||||
disk.Type = "PERSISTENT"
|
||||
disk.Mode = "READ_WRITE"
|
||||
disk.Boot = i == 0
|
||||
disk.Boot = i == 0 && !hasBootDisk
|
||||
disk.AutoDelete = d.Get(prefix + ".auto_delete").(bool)
|
||||
|
||||
if _, ok := d.GetOk(prefix + ".disk"); ok {
|
||||
@ -513,7 +605,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
|
||||
AutoDelete: false, // Don't allow autodelete; let terraform handle disk deletion
|
||||
}
|
||||
|
||||
disk.Boot = i == 0 && disksCount == 0 // TODO(danawillow): This is super hacky, let's just add a boot field.
|
||||
disk.Boot = i == 0 && disksCount == 0 && !hasBootDisk
|
||||
|
||||
if v, ok := d.GetOk(prefix + ".device_name"); ok {
|
||||
disk.DeviceName = v.(string)
|
||||
@ -868,9 +960,10 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
|
||||
|
||||
disksCount := d.Get("disk.#").(int)
|
||||
attachedDisksCount := d.Get("attached_disk.#").(int)
|
||||
disks := make([]map[string]interface{}, 0, disksCount)
|
||||
attachedDisks := make([]map[string]interface{}, 0, attachedDisksCount)
|
||||
|
||||
if _, ok := d.GetOk("boot_disk"); ok {
|
||||
disksCount++
|
||||
}
|
||||
if expectedDisks := disksCount + attachedDisksCount; len(instance.Disks) != expectedDisks {
|
||||
return fmt.Errorf("Expected %d disks, API returned %d", expectedDisks, len(instance.Disks))
|
||||
}
|
||||
@ -882,8 +975,14 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
|
||||
|
||||
dIndex := 0
|
||||
adIndex := 0
|
||||
disks := make([]map[string]interface{}, 0, disksCount)
|
||||
attachedDisks := make([]map[string]interface{}, 0, attachedDisksCount)
|
||||
for _, disk := range instance.Disks {
|
||||
if _, ok := attachedDiskSources[disk.Source]; !ok {
|
||||
if _, ok := d.GetOk("boot_disk"); ok && disk.Boot {
|
||||
// This disk is a boot disk and there is a boot disk set in the config, therefore
|
||||
// this is the boot disk set in the config.
|
||||
d.Set("boot_disk", flattenBootDisk(d, disk))
|
||||
} else if _, ok := attachedDiskSources[disk.Source]; !ok {
|
||||
di := map[string]interface{}{
|
||||
"disk": d.Get(fmt.Sprintf("disk.%d.disk", dIndex)),
|
||||
"image": d.Get(fmt.Sprintf("disk.%d.image", dIndex)),
|
||||
@ -1193,3 +1292,82 @@ func resourceInstanceTags(d *schema.ResourceData) *compute.Tags {
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
func expandBootDisk(d *schema.ResourceData, config *Config, zone *compute.Zone, project string) (*compute.AttachedDisk, error) {
|
||||
disk := &compute.AttachedDisk{
|
||||
AutoDelete: d.Get("boot_disk.0.auto_delete").(bool),
|
||||
Boot: true,
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("boot_disk.0.device_name"); ok {
|
||||
disk.DeviceName = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("boot_disk.0.disk_encryption_key_raw"); ok {
|
||||
disk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
|
||||
RawKey: v.(string),
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("boot_disk.0.source"); ok {
|
||||
diskName := v.(string)
|
||||
diskData, err := config.clientCompute.Disks.Get(
|
||||
project, zone.Name, diskName).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error loading disk '%s': %s", diskName, err)
|
||||
}
|
||||
disk.Source = diskData.SelfLink
|
||||
}
|
||||
|
||||
if _, ok := d.GetOk("boot_disk.0.initialize_params"); ok {
|
||||
disk.InitializeParams = &compute.AttachedDiskInitializeParams{}
|
||||
|
||||
if v, ok := d.GetOk("boot_disk.0.initialize_params.0.size"); ok {
|
||||
disk.InitializeParams.DiskSizeGb = int64(v.(int))
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("boot_disk.0.initialize_params.0.type"); ok {
|
||||
diskTypeName := v.(string)
|
||||
diskType, err := readDiskType(config, zone, diskTypeName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error loading disk type '%s': %s", diskTypeName, err)
|
||||
}
|
||||
disk.InitializeParams.DiskType = diskType.Name
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("boot_disk.0.initialize_params.0.image"); ok {
|
||||
imageName := v.(string)
|
||||
imageUrl, err := resolveImage(config, imageName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error resolving image name '%s': %s", imageName, err)
|
||||
}
|
||||
|
||||
disk.InitializeParams.SourceImage = imageUrl
|
||||
}
|
||||
}
|
||||
|
||||
return disk, nil
|
||||
}
|
||||
|
||||
func flattenBootDisk(d *schema.ResourceData, disk *compute.AttachedDisk) []map[string]interface{} {
|
||||
sourceUrl := strings.Split(disk.Source, "/")
|
||||
result := map[string]interface{}{
|
||||
"auto_delete": disk.AutoDelete,
|
||||
"device_name": disk.DeviceName,
|
||||
"source": sourceUrl[len(sourceUrl)-1],
|
||||
// disk_encryption_key_raw is not returned from the API, so don't store it in state.
|
||||
// If necessary in the future, this can be copied from what the user originally specified.
|
||||
}
|
||||
if disk.DiskEncryptionKey != nil {
|
||||
result["disk_encryption_key_sha256"] = disk.DiskEncryptionKey.Sha256
|
||||
}
|
||||
if v, ok := d.GetOk("boot_disk.0.initialize_params.#"); ok {
|
||||
result["initialize_params.#"] = v.(int)
|
||||
// initialize_params is not returned from the API, so don't store its values in state.
|
||||
// If necessary in the future, this can be copied from what the user originally specified.
|
||||
// However, because Terraform automatically sets `boot_disk.0.initialize_params.#` to 0 if
|
||||
// nothing is set in state for it, set it to whatever it was set to before to avoid a perpetual diff.
|
||||
}
|
||||
|
||||
return []map[string]interface{}{result}
|
||||
}
|
||||
|
@ -267,6 +267,49 @@ func TestAccComputeInstance_attachedDisk(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccComputeInstance_bootDisk(t *testing.T) {
|
||||
var instance compute.Instance
|
||||
var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10))
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckComputeInstanceDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeInstance_bootDisk(instanceName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeInstanceExists(
|
||||
"google_compute_instance.foobar", &instance),
|
||||
testAccCheckComputeInstanceBootDisk(&instance, instanceName),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccComputeInstance_bootDisk_source(t *testing.T) {
|
||||
var instance compute.Instance
|
||||
var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10))
|
||||
var diskName = fmt.Sprintf("instance-test-%s", acctest.RandString(10))
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckComputeInstanceDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeInstance_bootDisk_source(diskName, instanceName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeInstanceExists(
|
||||
"google_compute_instance.foobar", &instance),
|
||||
testAccCheckComputeInstanceBootDisk(&instance, diskName),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccComputeInstance_noDisk(t *testing.T) {
|
||||
var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10))
|
||||
|
||||
@ -277,7 +320,7 @@ func TestAccComputeInstance_noDisk(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeInstance_noDisk(instanceName),
|
||||
ExpectError: regexp.MustCompile("At least one disk or attached_disk must be set"),
|
||||
ExpectError: regexp.MustCompile("At least one disk, attached_disk, or boot_disk must be set"),
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -751,7 +794,7 @@ func testAccCheckComputeInstanceDisk(instance *compute.Instance, source string,
|
||||
}
|
||||
|
||||
for _, disk := range instance.Disks {
|
||||
if strings.LastIndex(disk.Source, "/"+source) == len(disk.Source)-len(source)-1 && disk.AutoDelete == delete && disk.Boot == boot {
|
||||
if strings.HasSuffix(disk.Source, source) && disk.AutoDelete == delete && disk.Boot == boot {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -760,6 +803,24 @@ func testAccCheckComputeInstanceDisk(instance *compute.Instance, source string,
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckComputeInstanceBootDisk(instance *compute.Instance, source string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if instance.Disks == nil {
|
||||
return fmt.Errorf("no disks")
|
||||
}
|
||||
|
||||
for _, disk := range instance.Disks {
|
||||
if disk.Boot == true {
|
||||
if strings.HasSuffix(disk.Source, source) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Boot disk not found with source %q", source)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckComputeInstanceDiskEncryptionKey(n string, instance *compute.Instance) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
@ -1232,6 +1293,51 @@ resource "google_compute_instance" "foobar" {
|
||||
`, disk, instance)
|
||||
}
|
||||
|
||||
func testAccComputeInstance_bootDisk(instance string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_compute_instance" "foobar" {
|
||||
name = "%s"
|
||||
machine_type = "n1-standard-1"
|
||||
zone = "us-central1-a"
|
||||
|
||||
boot_disk {
|
||||
initialize_params {
|
||||
image = "debian-8-jessie-v20160803"
|
||||
}
|
||||
disk_encryption_key_raw = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="
|
||||
}
|
||||
|
||||
network_interface {
|
||||
network = "default"
|
||||
}
|
||||
}
|
||||
`, instance)
|
||||
}
|
||||
|
||||
func testAccComputeInstance_bootDisk_source(disk, instance string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_compute_disk" "foobar" {
|
||||
name = "%s"
|
||||
zone = "us-central1-a"
|
||||
image = "debian-8-jessie-v20160803"
|
||||
}
|
||||
|
||||
resource "google_compute_instance" "foobar" {
|
||||
name = "%s"
|
||||
machine_type = "n1-standard-1"
|
||||
zone = "us-central1-a"
|
||||
|
||||
boot_disk {
|
||||
source = "${google_compute_disk.foobar.name}"
|
||||
}
|
||||
|
||||
network_interface {
|
||||
network = "default"
|
||||
}
|
||||
}
|
||||
`, disk, instance)
|
||||
}
|
||||
|
||||
func testAccComputeInstance_noDisk(instance string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_compute_instance" "foobar" {
|
||||
|
@ -58,8 +58,8 @@ resource "google_compute_instance" "default" {
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `disk` - (Required) Disks to attach to the instance. This can be specified
|
||||
multiple times for multiple disks. Structure is documented below.
|
||||
* `boot_disk` - (Required) The boot disk for the instance.
|
||||
Structure is documented below.
|
||||
|
||||
* `machine_type` - (Required) The machine type to create. To create a custom
|
||||
machine type, value should be set as specified
|
||||
@ -80,8 +80,16 @@ The following arguments are supported:
|
||||
packets with non-matching source or destination IPs.
|
||||
This defaults to false.
|
||||
|
||||
* `create_timeout` - (Optional) Configurable timeout in minutes for creating instances. Default is 4 minutes.
|
||||
Changing this forces a new resource to be created.
|
||||
|
||||
* `description` - (Optional) A brief description of this resource.
|
||||
|
||||
* `disk` - (Optional) Disks to attach to the instance. This can be specified
|
||||
multiple times for multiple disks. Structure is documented below.
|
||||
|
||||
* `labels` - (Optional) A set of key/value label pairs to assign to the instance.
|
||||
|
||||
* `metadata` - (Optional) Metadata key/value pairs to make available from
|
||||
within the instance.
|
||||
|
||||
@ -102,16 +110,46 @@ The following arguments are supported:
|
||||
|
||||
* `tags` - (Optional) A list of tags to attach to the instance.
|
||||
|
||||
* `labels` - (Optional) A set of key/value label pairs to assign to the instance.
|
||||
---
|
||||
|
||||
* `create_timeout` - (Optional) Configurable timeout in minutes for creating instances. Default is 4 minutes.
|
||||
Changing this forces a new resource to be created.
|
||||
* `network` - (DEPRECATED) Networks to attach to the instance. This
|
||||
can be specified multiple times for multiple networks. Structure is
|
||||
documented below.
|
||||
|
||||
---
|
||||
|
||||
* `network` - (DEPRECATED, Required) Networks to attach to the instance. This
|
||||
can be specified multiple times for multiple networks. Structure is
|
||||
documented below.
|
||||
The `boot_disk` block supports:
|
||||
|
||||
* `auto_delete` - (Optional) Whether the disk will be auto-deleted when the instance
|
||||
is deleted. Defaults to true.
|
||||
|
||||
* `device_name` - (Optional) Name with which attached disk will be accessible
|
||||
under `/dev/disk/by-id/`
|
||||
|
||||
* `disk_encryption_key_raw` - (Optional) A 256-bit [customer-supplied encryption key]
|
||||
(https://cloud.google.com/compute/docs/disks/customer-supplied-encryption),
|
||||
encoded in [RFC 4648 base64](https://tools.ietf.org/html/rfc4648#section-4)
|
||||
to encrypt this disk.
|
||||
|
||||
* `initialize_params` - (Optional) Parameters for a new disk that will be created
|
||||
alongside the new instance. Either `initialize_params` or `source` must be set.
|
||||
Structure is documented below.
|
||||
|
||||
* `source` - (Optional) The name of the existing disk (such as those managed by
|
||||
`google_compute_disk`) to attach.
|
||||
|
||||
The `initialize_params` block supports:
|
||||
|
||||
* `size` - (Optional) The size of the image in gigabytes. If not specified, it
|
||||
will inherit the size of its base image.
|
||||
|
||||
* `type` - (Optional) The GCE disk type. May be set to pd-standard or pd-ssd.
|
||||
|
||||
* `image` - (Optional) The image from which to initialize this disk. This can be
|
||||
one of: the image's `self_link`, `projects/{project}/global/images/{image}`,
|
||||
`projects/{project}/global/images/family/{family}`, `global/images/{image}`,
|
||||
`global/images/family/{family}`, `family/{family}`, `{project}/{family}`,
|
||||
`{project}/{image}`, `{family}`, or `{image}`.
|
||||
|
||||
The `disk` block supports: (Note that either disk or image is required, unless
|
||||
the type is "local-ssd", in which case scratch must be true).
|
||||
|
Loading…
Reference in New Issue
Block a user