package googlecompute import ( "context" "errors" "fmt" "strings" "testing" "time" "github.com/hashicorp/packer/packer-plugin-sdk/multistep" "github.com/hashicorp/packer/packer-plugin-sdk/template/config" "github.com/stretchr/testify/assert" ) func TestStepCreateInstance_impl(t *testing.T) { var _ multistep.Step = new(StepCreateInstance) } func TestStepCreateInstance(t *testing.T) { state := testState(t) step := new(StepCreateInstance) defer step.Cleanup(state) state.Put("ssh_public_key", "key") c := state.Get("config").(*Config) d := state.Get("driver").(*DriverMock) d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100) // run the step assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.") // Verify state nameRaw, ok := state.GetOk("instance_name") assert.True(t, ok, "State should have an instance name.") // cleanup step.Cleanup(state) // Check args passed to the driver. assert.Equal(t, d.DeleteInstanceName, nameRaw.(string), "Incorrect instance name passed to driver.") assert.Equal(t, d.DeleteInstanceZone, c.Zone, "Incorrect instance zone passed to driver.") assert.Equal(t, d.DeleteDiskName, c.InstanceName, "Incorrect disk name passed to driver.") assert.Equal(t, d.DeleteDiskZone, c.Zone, "Incorrect disk zone passed to driver.") } func TestStepCreateInstance_fromFamily(t *testing.T) { cases := []struct { Name string Family string Expect bool }{ {"test-image", "", false}, {"test-image", "test-family", false}, // name trumps family {"", "test-family", true}, } for _, tc := range cases { state := testState(t) step := new(StepCreateInstance) defer step.Cleanup(state) state.Put("ssh_public_key", "key") c := state.Get("config").(*Config) c.SourceImage = tc.Name c.SourceImageFamily = tc.Family d := state.Get("driver").(*DriverMock) d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100) // run the step assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.") // cleanup step.Cleanup(state) // Check args passed to the driver. if tc.Expect { assert.True(t, d.GetImageFromFamily, "Driver wasn't instructed to use an image family") } else { assert.False(t, d.GetImageFromFamily, "Driver was unexpectedly instructed to use an image family") } } } func TestStepCreateInstance_windowsNeedsPassword(t *testing.T) { state := testState(t) step := new(StepCreateInstance) defer step.Cleanup(state) state.Put("ssh_public_key", "key") c := state.Get("config").(*Config) d := state.Get("driver").(*DriverMock) d.GetImageResult = StubImage("test-image", "test-project", []string{"windows"}, 100) c.Comm.Type = "winrm" // run the step if action := step.Run(context.Background(), state); action != multistep.ActionContinue { t.Fatalf("bad action: %#v", action) } // Verify state nameRaw, ok := state.GetOk("instance_name") if !ok { t.Fatal("should have instance name") } createPassword, ok := state.GetOk("create_windows_password") if !ok || !createPassword.(bool) { t.Fatal("should need to create a windows password") } // cleanup step.Cleanup(state) if d.DeleteInstanceName != nameRaw.(string) { t.Fatal("should've deleted instance") } if d.DeleteInstanceZone != c.Zone { t.Fatalf("bad instance zone: %#v", d.DeleteInstanceZone) } if d.DeleteDiskName != c.InstanceName { t.Fatal("should've deleted disk") } if d.DeleteDiskZone != c.Zone { t.Fatalf("bad disk zone: %#v", d.DeleteDiskZone) } } func TestStepCreateInstance_windowsPasswordSet(t *testing.T) { state := testState(t) step := new(StepCreateInstance) defer step.Cleanup(state) state.Put("ssh_public_key", "key") config := state.Get("config").(*Config) driver := state.Get("driver").(*DriverMock) driver.GetImageResult = StubImage("test-image", "test-project", []string{"windows"}, 100) config.Comm.Type = "winrm" config.Comm.WinRMPassword = "password" // run the step if action := step.Run(context.Background(), state); action != multistep.ActionContinue { t.Fatalf("bad action: %#v", action) } // Verify state nameRaw, ok := state.GetOk("instance_name") if !ok { t.Fatal("should have instance name") } _, ok = state.GetOk("create_windows_password") if ok { t.Fatal("should not need to create windows password") } // cleanup step.Cleanup(state) if driver.DeleteInstanceName != nameRaw.(string) { t.Fatal("should've deleted instance") } if driver.DeleteInstanceZone != config.Zone { t.Fatalf("bad instance zone: %#v", driver.DeleteInstanceZone) } if driver.DeleteDiskName != config.InstanceName { t.Fatal("should've deleted disk") } if driver.DeleteDiskZone != config.Zone { t.Fatalf("bad disk zone: %#v", driver.DeleteDiskZone) } } func TestStepCreateInstance_error(t *testing.T) { state := testState(t) step := new(StepCreateInstance) defer step.Cleanup(state) state.Put("ssh_public_key", "key") d := state.Get("driver").(*DriverMock) d.RunInstanceErr = errors.New("error") d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100) // run the step assert.Equal(t, step.Run(context.Background(), state), multistep.ActionHalt, "Step should have failed and halted.") // Verify state _, ok := state.GetOk("error") assert.True(t, ok, "State should have an error.") _, ok = state.GetOk("instance_name") assert.False(t, ok, "State should not have an instance name.") } func TestStepCreateInstance_errorOnChannel(t *testing.T) { state := testState(t) step := new(StepCreateInstance) defer step.Cleanup(state) state.Put("ssh_public_key", "key") errCh := make(chan error, 1) errCh <- errors.New("error") d := state.Get("driver").(*DriverMock) d.RunInstanceErrCh = errCh d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100) // run the step assert.Equal(t, step.Run(context.Background(), state), multistep.ActionHalt, "Step should have failed and halted.") // Verify state _, ok := state.GetOk("error") assert.True(t, ok, "State should have an error.") _, ok = state.GetOk("instance_name") assert.False(t, ok, "State should not have an instance name.") } func TestStepCreateInstance_errorTimeout(t *testing.T) { state := testState(t) step := new(StepCreateInstance) defer step.Cleanup(state) state.Put("ssh_public_key", "key") errCh := make(chan error, 1) config := state.Get("config").(*Config) config.StateTimeout = 1 * time.Millisecond d := state.Get("driver").(*DriverMock) d.RunInstanceErrCh = errCh d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100) // run the step assert.Equal(t, step.Run(context.Background(), state), multistep.ActionHalt, "Step should have failed and halted.") // Verify state _, ok := state.GetOk("error") assert.True(t, ok, "State should have an error.") _, ok = state.GetOk("instance_name") assert.False(t, ok, "State should not have an instance name.") } func TestStepCreateInstance_noServiceAccount(t *testing.T) { state := testState(t) step := new(StepCreateInstance) defer step.Cleanup(state) state.Put("ssh_public_key", "key") c := state.Get("config").(*Config) c.DisableDefaultServiceAccount = true c.ServiceAccountEmail = "" d := state.Get("driver").(*DriverMock) d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100) // run the step assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.") // cleanup step.Cleanup(state) // Check args passed to the driver. assert.Equal(t, d.RunInstanceConfig.DisableDefaultServiceAccount, c.DisableDefaultServiceAccount, "Incorrect value for DisableDefaultServiceAccount passed to driver.") assert.Equal(t, d.RunInstanceConfig.ServiceAccountEmail, c.ServiceAccountEmail, "Incorrect value for ServiceAccountEmail passed to driver.") } func TestStepCreateInstance_customServiceAccount(t *testing.T) { state := testState(t) step := new(StepCreateInstance) defer step.Cleanup(state) state.Put("ssh_public_key", "key") c := state.Get("config").(*Config) c.DisableDefaultServiceAccount = true c.ServiceAccountEmail = "custom-service-account" d := state.Get("driver").(*DriverMock) d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100) // run the step assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.") // cleanup step.Cleanup(state) // Check args passed to the driver. assert.Equal(t, d.RunInstanceConfig.DisableDefaultServiceAccount, c.DisableDefaultServiceAccount, "Incorrect value for DisableDefaultServiceAccount passed to driver.") assert.Equal(t, d.RunInstanceConfig.ServiceAccountEmail, c.ServiceAccountEmail, "Incorrect value for ServiceAccountEmail passed to driver.") } func TestCreateInstanceMetadata(t *testing.T) { state := testState(t) c := state.Get("config").(*Config) image := StubImage("test-image", "test-project", []string{}, 100) key := "abcdefgh12345678" // create our metadata _, metadataSSHKeys, err := c.createInstanceMetadata(image, key) assert.True(t, err == nil, "Metadata creation should have succeeded.") // ensure our key is listed assert.True(t, strings.Contains(metadataSSHKeys["ssh-keys"], key), "Instance metadata should contain provided key") } func TestCreateInstanceMetadata_noPublicKey(t *testing.T) { state := testState(t) c := state.Get("config").(*Config) image := StubImage("test-image", "test-project", []string{}, 100) sshKeys := c.Metadata["ssh-keys"] // create our metadata _, metadataSSHKeys, err := c.createInstanceMetadata(image, "") assert.True(t, err == nil, "Metadata creation should have succeeded.") // ensure the ssh metadata hasn't changed assert.Equal(t, metadataSSHKeys["ssh-keys"], sshKeys, "Instance metadata should not have been modified") } func TestCreateInstanceMetadata_metadataFile(t *testing.T) { state := testState(t) c := state.Get("config").(*Config) image := StubImage("test-image", "test-project", []string{}, 100) content := testMetadataFileContent fileName := testMetadataFile(t) c.MetadataFiles["user-data"] = fileName // create our metadata metadataNoSSHKeys, _,err := c.createInstanceMetadata(image, "") assert.True(t, err == nil, "Metadata creation should have succeeded.") // ensure the user-data key in metadata is updated with file content assert.Equal(t, metadataNoSSHKeys["user-data"], content, "user-data field of the instance metadata should have been updated.") } func TestCreateInstanceMetadata_withWrapStartupScript(t *testing.T) { tt := []struct { WrapStartupScript config.Trilean StartupScriptContents string WrappedStartupScriptContents string WrappedStartupScriptStatus string }{ { WrapStartupScript: config.TriUnset, StartupScriptContents: testMetadataFileContent, }, { WrapStartupScript: config.TriFalse, StartupScriptContents: testMetadataFileContent, }, { WrapStartupScript: config.TriTrue, StartupScriptContents: StartupScriptLinux, WrappedStartupScriptContents: testMetadataFileContent, WrappedStartupScriptStatus: StartupScriptStatusNotDone, }, } for _, tc := range tt { tc := tc state := testState(t) image := StubImage("test-image", "test-project", []string{}, 100) c := state.Get("config").(*Config) c.StartupScriptFile = testMetadataFile(t) c.WrapStartupScriptFile = tc.WrapStartupScript // create our metadata metadataNoSSHKeys, _, err := c.createInstanceMetadata(image, "") assert.True(t, err == nil, "Metadata creation should have succeeded.") assert.Equal(t, tc.StartupScriptContents, metadataNoSSHKeys[StartupScriptKey], fmt.Sprintf("Instance metadata for startup script should be %q.", tc.StartupScriptContents)) assert.Equal(t, tc.WrappedStartupScriptContents, metadataNoSSHKeys[StartupWrappedScriptKey], fmt.Sprintf("Instance metadata for wrapped startup script should be %q.", tc.WrappedStartupScriptContents)) assert.Equal(t, tc.WrappedStartupScriptStatus, metadataNoSSHKeys[StartupScriptStatusKey], fmt.Sprintf("Instance metadata startup script status should be %q.", tc.WrappedStartupScriptStatus)) } } func TestCreateInstanceMetadataWaitToAddSSHKeys(t *testing.T) { state := testState(t) c := state.Get("config").(*Config) image := StubImage("test-image", "test-project", []string{}, 100) key := "abcdefgh12345678" var waitTime int = 4 c.WaitToAddSSHKeys = time.Duration(waitTime) * time.Second c.Metadata = map[string]string{ "metadatakey1": "xyz", "metadatakey2": "123", } // create our metadata metadataNoSSHKeys, metadataSSHKeys, err := c.createInstanceMetadata(image, key) assert.True(t, err == nil, "Metadata creation should have succeeded.") // ensure our metadata is listed assert.True(t, strings.Contains(metadataSSHKeys["ssh-keys"], key), "Instance metadata should contain provided SSH key") assert.True(t, strings.Contains(metadataNoSSHKeys["metadatakey1"], "xyz"), "Instance metadata should contain provided key: metadatakey1") assert.True(t, strings.Contains(metadataNoSSHKeys["metadatakey2"], "123"), "Instance metadata should contain provided key: metadatakey2") } func TestStepCreateInstanceWaitToAddSSHKeys(t *testing.T) { state := testState(t) step := new(StepCreateInstance) defer step.Cleanup(state) state.Put("ssh_public_key", "key") c := state.Get("config").(*Config) d := state.Get("driver").(*DriverMock) d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100) key := "abcdefgh12345678" var waitTime int = 5 c.WaitToAddSSHKeys = time.Duration(waitTime) * time.Second c.Comm.SSHPublicKey = []byte(key) c.Metadata = map[string]string{ "metadatakey1": "xyz", "metadatakey2": "123", } // run the step assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.") // Verify state _, ok := state.GetOk("instance_name") assert.True(t, ok, "State should have an instance name.") // cleanup step.Cleanup(state) } func TestStepCreateInstanceNoWaitToAddSSHKeys(t *testing.T) { state := testState(t) step := new(StepCreateInstance) defer step.Cleanup(state) state.Put("ssh_public_key", "key") c := state.Get("config").(*Config) d := state.Get("driver").(*DriverMock) d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100) key := "abcdefgh12345678" c.Comm.SSHPublicKey = []byte(key) c.Metadata = map[string]string{ "metadatakey1": "xyz", "metadatakey2": "123", } // run the step assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.") // Verify state _, ok := state.GetOk("instance_name") assert.True(t, ok, "State should have an instance name.") // cleanup step.Cleanup(state) }