diff --git a/google/resource_compute_instance.go b/google/resource_compute_instance.go index 36819ed9..dd7ca7f9 100644 --- a/google/resource_compute_instance.go +++ b/google/resource_compute_instance.go @@ -40,7 +40,7 @@ func resourceComputeInstance() *schema.Resource { Update: resourceComputeInstanceUpdate, Delete: resourceComputeInstanceDelete, - SchemaVersion: 4, + SchemaVersion: 5, MigrateState: resourceComputeInstanceMigrateState, Schema: map[string]*schema.Schema{ diff --git a/google/resource_compute_instance_migrate.go b/google/resource_compute_instance_migrate.go index f4e58603..feb7fe1c 100644 --- a/google/resource_compute_instance_migrate.go +++ b/google/resource_compute_instance_migrate.go @@ -19,35 +19,45 @@ func resourceComputeInstanceMigrateState( return is, nil } + var err error + switch v { case 0: log.Println("[INFO] Found Compute Instance State v0; migrating to v1") - is, err := migrateStateV0toV1(is) + is, err = migrateStateV0toV1(is) if err != nil { return is, err } fallthrough case 1: log.Println("[INFO] Found Compute Instance State v1; migrating to v2") - is, err := migrateStateV1toV2(is) + is, err = migrateStateV1toV2(is) if err != nil { return is, err } - return is, nil + fallthrough case 2: log.Println("[INFO] Found Compute Instance State v2; migrating to v3") - is, err := migrateStateV2toV3(is) + is, err = migrateStateV2toV3(is) if err != nil { return is, err } - return is, nil + fallthrough case 3: log.Println("[INFO] Found Compute Instance State v3; migrating to v4") - is, err := migrateStateV3toV4(is, meta) + is, err = migrateStateV3toV4(is, meta) if err != nil { return is, err } - return is, nil + fallthrough + case 4: + log.Println("[INFO] Found Compute Instance State v4; migrating to v5") + is, err = migrateStateV4toV5(is, meta) + if err != nil { + return is, err + } + // when adding case 5, make sure to turn this into a fallthrough + return is, err default: return is, fmt.Errorf("Unexpected schema version: %d", v) } @@ -274,6 +284,13 @@ func migrateStateV3toV4(is *terraform.InstanceState, meta interface{}) (*terrafo return is, nil } +func migrateStateV4toV5(is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { + if v := is.Attributes["disk.#"]; v != "" { + return migrateStateV3toV4(is, meta) + } + return is, nil +} + func getInstanceFromInstanceState(config *Config, is *terraform.InstanceState) (*compute.Instance, error) { project, ok := is.Attributes["project"] if !ok { diff --git a/google/resource_compute_instance_migrate_test.go b/google/resource_compute_instance_migrate_test.go index 125bc6a1..061d1259 100644 --- a/google/resource_compute_instance_migrate_test.go +++ b/google/resource_compute_instance_migrate_test.go @@ -14,6 +14,9 @@ import ( ) func TestComputeInstanceMigrateState(t *testing.T) { + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } cases := map[string]struct { StateVersion int Attributes map[string]string @@ -70,6 +73,9 @@ func TestComputeInstanceMigrateState(t *testing.T) { } func TestComputeInstanceMigrateState_empty(t *testing.T) { + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } var is *terraform.InstanceState var meta interface{} @@ -148,9 +154,71 @@ func TestAccComputeInstanceMigrateState_bootDisk(t *testing.T) { "boot_disk.0.disk_encryption_key_sha256": "encrypt-key-sha", "boot_disk.0.source": instanceName, "zone": zone, + "create_timeout": "4", } - runInstanceMigrateTest(t, instanceName, "migrate disk to boot disk", 3 /* state version */, attributes, expected, config) + runInstanceMigrateTest(t, instanceName, "migrate disk to boot disk", 2 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_v4FixBootDisk(t *testing.T) { + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + // Seed test data + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-8", + }, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err := config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr := computeSharedOperationWait(config, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + + attributes := map[string]string{ + "disk.#": "1", + "disk.0.disk": "disk-1", + "disk.0.type": "pd-ssd", + "disk.0.auto_delete": "false", + "disk.0.size": "12", + "disk.0.device_name": "persistent-disk-0", + "disk.0.disk_encryption_key_raw": "encrypt-key", + "disk.0.disk_encryption_key_sha256": "encrypt-key-sha", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "boot_disk.0.auto_delete": "false", + "boot_disk.0.device_name": "persistent-disk-0", + "boot_disk.0.disk_encryption_key_raw": "encrypt-key", + "boot_disk.0.disk_encryption_key_sha256": "encrypt-key-sha", + "boot_disk.0.source": instanceName, + "zone": zone, + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to boot disk", 4 /* state version */, attributes, expected, config) } func TestAccComputeInstanceMigrateState_attachedDiskFromSource(t *testing.T) { @@ -208,6 +276,84 @@ func TestAccComputeInstanceMigrateState_attachedDiskFromSource(t *testing.T) { } defer cleanUpInstance(config, instanceName, zone) + attributes := map[string]string{ + "boot_disk.#": "1", + "disk.#": "1", + "disk.0.disk": diskName, + "disk.0.device_name": "persistent-disk-1", + "disk.0.disk_encryption_key_raw": "encrypt-key", + "disk.0.disk_encryption_key_sha256": "encrypt-key-sha", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "attached_disk.#": "1", + "attached_disk.0.source": "https://www.googleapis.com/compute/v1/projects/" + config.Project + "/zones/" + zone + "/disks/" + diskName, + "attached_disk.0.device_name": "persistent-disk-1", + "attached_disk.0.disk_encryption_key_raw": "encrypt-key", + "attached_disk.0.disk_encryption_key_sha256": "encrypt-key-sha", + "zone": zone, + "create_timeout": "4", + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 2 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_v4FixAttachedDiskFromSource(t *testing.T) { + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + // Seed test data + diskName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + disk := &compute.Disk{ + Name: diskName, + SourceImage: "projects/debian-cloud/global/images/family/debian-8", + Zone: zone, + } + op, err := config.clientCompute.Disks.Insert(config.Project, zone, disk).Do() + if err != nil { + t.Fatalf("Error creating disk: %s", err) + } + waitErr := computeSharedOperationWait(config, op, config.Project, "disk to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpDisk(config, diskName, zone) + + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-8", + }, + }, + { + Source: "projects/" + config.Project + "/zones/" + zone + "/disks/" + diskName, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err = config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr = computeSharedOperationWait(config, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + attributes := map[string]string{ "boot_disk.#": "1", "disk.#": "1", @@ -227,7 +373,7 @@ func TestAccComputeInstanceMigrateState_attachedDiskFromSource(t *testing.T) { "zone": zone, } - runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 3 /* state version */, attributes, expected, config) + runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 4 /* state version */, attributes, expected, config) } func TestAccComputeInstanceMigrateState_attachedDiskFromEncryptionKey(t *testing.T) { @@ -274,6 +420,72 @@ func TestAccComputeInstanceMigrateState_attachedDiskFromEncryptionKey(t *testing } defer cleanUpInstance(config, instanceName, zone) + attributes := map[string]string{ + "boot_disk.#": "1", + "disk.#": "1", + "disk.0.image": "projects/debian-cloud/global/images/family/debian-8", + "disk.0.disk_encryption_key_raw": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=", + "disk.0.disk_encryption_key_sha256": "esTuF7d4eatX4cnc4JsiEiaI+Rff78JgPhA/v1zxX9E=", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "attached_disk.#": "1", + "attached_disk.0.source": "https://www.googleapis.com/compute/v1/projects/" + config.Project + "/zones/" + zone + "/disks/" + instanceName + "-1", + "attached_disk.0.device_name": "persistent-disk-1", + "attached_disk.0.disk_encryption_key_raw": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=", + "attached_disk.0.disk_encryption_key_sha256": "esTuF7d4eatX4cnc4JsiEiaI+Rff78JgPhA/v1zxX9E=", + "zone": zone, + "create_timeout": "4", + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 2 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_v4FixAttachedDiskFromEncryptionKey(t *testing.T) { + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-8", + }, + }, + { + AutoDelete: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-8", + }, + DiskEncryptionKey: &compute.CustomerEncryptionKey{ + RawKey: "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=", + }, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err := config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr := computeSharedOperationWait(config, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + attributes := map[string]string{ "boot_disk.#": "1", "disk.#": "1", @@ -292,7 +504,7 @@ func TestAccComputeInstanceMigrateState_attachedDiskFromEncryptionKey(t *testing "zone": zone, } - runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 3 /* state version */, attributes, expected, config) + runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 4 /* state version */, attributes, expected, config) } func TestAccComputeInstanceMigrateState_attachedDiskFromAutoDeleteAndImage(t *testing.T) { @@ -342,6 +554,76 @@ func TestAccComputeInstanceMigrateState_attachedDiskFromAutoDeleteAndImage(t *te } defer cleanUpInstance(config, instanceName, zone) + attributes := map[string]string{ + "boot_disk.#": "1", + "disk.#": "2", + "disk.0.image": "projects/debian-cloud/global/images/debian-8-jessie-v20170110", + "disk.0.auto_delete": "true", + "disk.1.image": "global/images/family/debian-8", + "disk.1.auto_delete": "true", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "attached_disk.#": "2", + "attached_disk.0.source": "https://www.googleapis.com/compute/v1/projects/" + config.Project + "/zones/" + zone + "/disks/" + instanceName + "-2", + "attached_disk.0.device_name": "persistent-disk-2", + "attached_disk.1.source": "https://www.googleapis.com/compute/v1/projects/" + config.Project + "/zones/" + zone + "/disks/" + instanceName + "-1", + "attached_disk.1.device_name": "persistent-disk-1", + "zone": zone, + "create_timeout": "4", + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 2 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_v4FixAttachedDiskFromAutoDeleteAndImage(t *testing.T) { + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-8", + }, + }, + { + AutoDelete: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-8", + }, + }, + { + AutoDelete: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/debian-8-jessie-v20170110", + }, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err := config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr := computeSharedOperationWait(config, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + attributes := map[string]string{ "boot_disk.#": "1", "disk.#": "2", @@ -361,7 +643,7 @@ func TestAccComputeInstanceMigrateState_attachedDiskFromAutoDeleteAndImage(t *te "zone": zone, } - runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 3 /* state version */, attributes, expected, config) + runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 4 /* state version */, attributes, expected, config) } func TestAccComputeInstanceMigrateState_scratchDisk(t *testing.T) { @@ -407,6 +689,68 @@ func TestAccComputeInstanceMigrateState_scratchDisk(t *testing.T) { } defer cleanUpInstance(config, instanceName, zone) + attributes := map[string]string{ + "boot_disk.#": "1", + "disk.#": "1", + "disk.0.auto_delete": "true", + "disk.0.type": "local-ssd", + "disk.0.scratch": "true", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "scratch_disk.#": "1", + "scratch_disk.0.interface": "SCSI", + "zone": zone, + "create_timeout": "4", + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to scratch disk", 2 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_v4FixScratchDisk(t *testing.T) { + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + // Seed test data + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-8", + }, + }, + { + AutoDelete: true, + Type: "SCRATCH", + InitializeParams: &compute.AttachedDiskInitializeParams{ + DiskType: "zones/" + zone + "/diskTypes/local-ssd", + }, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err := config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr := computeSharedOperationWait(config, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + attributes := map[string]string{ "boot_disk.#": "1", "disk.#": "1", @@ -422,7 +766,7 @@ func TestAccComputeInstanceMigrateState_scratchDisk(t *testing.T) { "zone": zone, } - runInstanceMigrateTest(t, instanceName, "migrate disk to scratch disk", 3 /* state version */, attributes, expected, config) + runInstanceMigrateTest(t, instanceName, "migrate disk to scratch disk", 4 /* state version */, attributes, expected, config) } func runInstanceMigrateTest(t *testing.T, id, testName string, version int, attributes, expected map[string]string, meta interface{}) {