Merge pull request #3932 from pieter-lazzaro/googlecompute-createpassword
GCE Windows instance password creation
This commit is contained in:
commit
924c0bc461
|
@ -58,13 +58,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
&StepCreateInstance{
|
||||
Debug: b.config.PackerDebug,
|
||||
},
|
||||
&StepCreateWindowsPassword{
|
||||
Debug: b.config.PackerDebug,
|
||||
DebugKeyPath: fmt.Sprintf("gce_windows_%s.pem", b.config.PackerBuildName),
|
||||
},
|
||||
&StepInstanceInfo{
|
||||
Debug: b.config.PackerDebug,
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Comm,
|
||||
Host: commHost,
|
||||
SSHConfig: sshConfig,
|
||||
Config: &b.config.Comm,
|
||||
Host: commHost,
|
||||
SSHConfig: sshConfig,
|
||||
WinRMConfig: winrmConfig,
|
||||
},
|
||||
new(common.StepProvision),
|
||||
new(StepWaitInstanceStartup),
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package googlecompute
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Driver is the interface that has to be implemented to communicate
|
||||
// with GCE. The Driver interface exists mostly to allow a mock implementation
|
||||
// to be used to test the steps.
|
||||
|
@ -44,6 +49,9 @@ type Driver interface {
|
|||
|
||||
// WaitForInstance waits for an instance to reach the given state.
|
||||
WaitForInstance(state, zone, name string) <-chan error
|
||||
|
||||
// CreateOrResetWindowsPassword creates or resets the password for a user on an Windows instance.
|
||||
CreateOrResetWindowsPassword(zone, name string, config *WindowsPasswordConfig) (<-chan error, error)
|
||||
}
|
||||
|
||||
type InstanceConfig struct {
|
||||
|
@ -64,3 +72,24 @@ type InstanceConfig struct {
|
|||
Tags []string
|
||||
Zone string
|
||||
}
|
||||
|
||||
// WindowsPasswordConfig is the data structue that GCE needs to encrypt the created
|
||||
// windows password.
|
||||
type WindowsPasswordConfig struct {
|
||||
key *rsa.PrivateKey
|
||||
password string
|
||||
UserName string `json:"userName"`
|
||||
Modulus string `json:"modulus"`
|
||||
Exponent string `json:"exponent"`
|
||||
Email string `json:"email"`
|
||||
ExpireOn time.Time `json:"expireOn"`
|
||||
}
|
||||
|
||||
type windowsPasswordResponse struct {
|
||||
UserName string `json:"userName"`
|
||||
PasswordFound bool `json:"passwordFound"`
|
||||
EncryptedPassword string `json:"encryptedPassword"`
|
||||
Modulus string `json:"modulus"`
|
||||
Exponent string `json:"exponent"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
}
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
package googlecompute
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
|
@ -394,6 +401,112 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
|||
return errCh, nil
|
||||
}
|
||||
|
||||
func (d *driverGCE) CreateOrResetWindowsPassword(instance, zone string, c *WindowsPasswordConfig) (<-chan error, error) {
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go d.createWindowsPassword(errCh, instance, zone, c)
|
||||
|
||||
return errCh, nil
|
||||
}
|
||||
|
||||
func (d *driverGCE) createWindowsPassword(errCh chan<- error, name, zone string, c *WindowsPasswordConfig) {
|
||||
|
||||
data, err := json.Marshal(c)
|
||||
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
dCopy := string(data)
|
||||
|
||||
instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
|
||||
instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{Key: "windows-keys", Value: &dCopy})
|
||||
|
||||
op, err := d.service.Instances.SetMetadata(d.projectId, zone, name, &compute.Metadata{
|
||||
Fingerprint: instance.Metadata.Fingerprint,
|
||||
Items: instance.Metadata.Items,
|
||||
}).Do()
|
||||
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
|
||||
newErrCh := make(chan error, 1)
|
||||
go waitForState(newErrCh, "DONE", d.refreshZoneOp(zone, op))
|
||||
|
||||
select {
|
||||
case err = <-newErrCh:
|
||||
case <-time.After(time.Second * 30):
|
||||
err = errors.New("time out while waiting for instance to create")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
|
||||
timeout := time.Now().Add(time.Minute * 3)
|
||||
hash := sha1.New()
|
||||
random := rand.Reader
|
||||
|
||||
for time.Now().Before(timeout) {
|
||||
if passwordResponses, err := d.getPasswordResponses(zone, name); err == nil {
|
||||
for _, response := range passwordResponses {
|
||||
if response.Modulus == c.Modulus {
|
||||
|
||||
decodedPassword, err := base64.StdEncoding.DecodeString(response.EncryptedPassword)
|
||||
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
password, err := rsa.DecryptOAEP(hash, random, c.key, decodedPassword, nil)
|
||||
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
|
||||
c.password = string(password)
|
||||
errCh <- nil
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
err = errors.New("Could not retrieve password. Timed out.")
|
||||
|
||||
errCh <- err
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (d *driverGCE) getPasswordResponses(zone, instance string) ([]windowsPasswordResponse, error) {
|
||||
output, err := d.service.Instances.GetSerialPortOutput(d.projectId, zone, instance).Port(4).Do()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responses := strings.Split(output.Contents, "\n")
|
||||
|
||||
passwordResponses := make([]windowsPasswordResponse, 0, len(responses))
|
||||
|
||||
for _, response := range responses {
|
||||
var passwordResponse windowsPasswordResponse
|
||||
if err := json.Unmarshal([]byte(response), &passwordResponse); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
passwordResponses = append(passwordResponses, passwordResponse)
|
||||
}
|
||||
|
||||
return passwordResponses, nil
|
||||
}
|
||||
|
||||
func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error {
|
||||
errCh := make(chan error, 1)
|
||||
go waitForState(errCh, state, d.refreshInstanceState(zone, name))
|
||||
|
|
|
@ -67,6 +67,12 @@ type DriverMock struct {
|
|||
RunInstanceErrCh <-chan error
|
||||
RunInstanceErr error
|
||||
|
||||
CreateOrResetWindowsPasswordZone string
|
||||
CreateOrResetWindowsPasswordInstance string
|
||||
CreateOrResetWindowsPasswordConfig *WindowsPasswordConfig
|
||||
CreateOrResetWindowsPasswordErr error
|
||||
CreateOrResetWindowsPasswordErrCh <-chan error
|
||||
|
||||
WaitForInstanceState string
|
||||
WaitForInstanceZone string
|
||||
WaitForInstanceName string
|
||||
|
@ -224,3 +230,25 @@ func (d *DriverMock) WaitForInstance(state, zone, name string) <-chan error {
|
|||
|
||||
return resultCh
|
||||
}
|
||||
|
||||
func (d *DriverMock) GetWindowsPassword() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (d *DriverMock) CreateOrResetWindowsPassword(instance, zone string, c *WindowsPasswordConfig) (<-chan error, error) {
|
||||
|
||||
d.CreateOrResetWindowsPasswordInstance = instance
|
||||
d.CreateOrResetWindowsPasswordZone = zone
|
||||
d.CreateOrResetWindowsPasswordConfig = c
|
||||
|
||||
c.password = "MOCK_PASSWORD"
|
||||
|
||||
resultCh := d.CreateOrResetWindowsPasswordErrCh
|
||||
if resultCh == nil {
|
||||
ch := make(chan error)
|
||||
close(ch)
|
||||
resultCh = ch
|
||||
}
|
||||
|
||||
return resultCh, d.CreateOrResetWindowsPasswordErr
|
||||
}
|
||||
|
|
|
@ -76,6 +76,10 @@ func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if sourceImage.IsWindows() && c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" {
|
||||
state.Put("create_windows_password", true)
|
||||
}
|
||||
|
||||
ui.Say("Creating instance...")
|
||||
name := c.InstanceName
|
||||
|
||||
|
|
|
@ -41,6 +41,101 @@ func TestStepCreateInstance(t *testing.T) {
|
|||
assert.Equal(t, d.DeleteDiskZone, c.Zone, "Incorrect disk zone passed to driver.")
|
||||
}
|
||||
|
||||
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(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(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)
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
package googlecompute
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepCreateWindowsPassword represents a Packer build step that sets the windows password on a Windows GCE instance.
|
||||
type StepCreateWindowsPassword struct {
|
||||
Debug bool
|
||||
DebugKeyPath string
|
||||
}
|
||||
|
||||
// Run executes the Packer build step that sets the windows password on a Windows GCE instance.
|
||||
func (s *StepCreateWindowsPassword) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
d := state.Get("driver").(Driver)
|
||||
c := state.Get("config").(*Config)
|
||||
name := state.Get("instance_name").(string)
|
||||
|
||||
if c.Comm.WinRMPassword != "" {
|
||||
state.Put("winrm_password", c.Comm.WinRMPassword)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
create, ok := state.GetOk("create_windows_password")
|
||||
|
||||
if !ok || !create.(bool) {
|
||||
return multistep.ActionContinue
|
||||
|
||||
}
|
||||
ui.Say("Creating windows user for instance...")
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating temporary key: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
buf := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(buf, uint32(priv.E))
|
||||
|
||||
data := WindowsPasswordConfig{
|
||||
key: priv,
|
||||
UserName: c.Comm.WinRMUser,
|
||||
Modulus: base64.StdEncoding.EncodeToString(priv.N.Bytes()),
|
||||
Exponent: base64.StdEncoding.EncodeToString(buf[1:]),
|
||||
Email: c.Account.ClientEmail,
|
||||
ExpireOn: time.Now().Add(time.Minute * 5),
|
||||
}
|
||||
|
||||
if s.Debug {
|
||||
|
||||
priv_blk := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
||||
f, err := os.Create(s.DebugKeyPath)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Write out the key
|
||||
err = pem.Encode(f, &priv_blk)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
errCh, err := d.CreateOrResetWindowsPassword(name, c.Zone, &data)
|
||||
|
||||
if err == nil {
|
||||
ui.Message("Waiting for windows password to complete...")
|
||||
select {
|
||||
case err = <-errCh:
|
||||
case <-time.After(c.stateTimeout):
|
||||
err = errors.New("time out while waiting for the password to be created")
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating windows password: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Message("Created password.")
|
||||
|
||||
if s.Debug {
|
||||
ui.Message(fmt.Sprintf(
|
||||
"Password (since debug is enabled): %s", data.password))
|
||||
}
|
||||
|
||||
state.Put("winrm_password", data.password)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Nothing to clean up. The windows password is only created on the single instance.
|
||||
func (s *StepCreateWindowsPassword) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,162 @@
|
|||
package googlecompute
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStepCreateOrResetWindowsPassword(t *testing.T) {
|
||||
state := testState(t)
|
||||
|
||||
// Step is run after the instance is created so we will have an instance name set
|
||||
state.Put("instance_name", "mock_instance")
|
||||
state.Put("create_windows_password", true)
|
||||
|
||||
step := new(StepCreateWindowsPassword)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if password, ok := state.GetOk("winrm_password"); !ok || password.(string) != "MOCK_PASSWORD" {
|
||||
t.Fatal("should have a password", password, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateOrResetWindowsPassword_passwordSet(t *testing.T) {
|
||||
state := testState(t)
|
||||
|
||||
// Step is run after the instance is created so we will have an instance name set
|
||||
state.Put("instance_name", "mock_instance")
|
||||
|
||||
c := state.Get("config").(*Config)
|
||||
|
||||
c.Comm.WinRMPassword = "password"
|
||||
|
||||
step := new(StepCreateWindowsPassword)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if password, ok := state.GetOk("winrm_password"); !ok || password.(string) != "password" {
|
||||
t.Fatal("should have used existing password", password, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateOrResetWindowsPassword_dontNeedPassword(t *testing.T) {
|
||||
state := testState(t)
|
||||
|
||||
// Step is run after the instance is created so we will have an instance name set
|
||||
state.Put("instance_name", "mock_instance")
|
||||
|
||||
step := new(StepCreateWindowsPassword)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStepCreateOrResetWindowsPassword_debug(t *testing.T) {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
|
||||
state := testState(t)
|
||||
// Step is run after the instance is created so we will have an instance name set
|
||||
state.Put("instance_name", "mock_instance")
|
||||
state.Put("create_windows_password", true)
|
||||
|
||||
step := new(StepCreateWindowsPassword)
|
||||
|
||||
step.Debug = true
|
||||
step.DebugKeyPath = tf.Name()
|
||||
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if password, ok := state.GetOk("winrm_password"); !ok || password.(string) != "MOCK_PASSWORD" {
|
||||
t.Fatal("should have a password", password, ok)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(tf.Name()); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateOrResetWindowsPassword_error(t *testing.T) {
|
||||
state := testState(t)
|
||||
|
||||
// Step is run after the instance is created so we will have an instance name set
|
||||
state.Put("instance_name", "mock_instance")
|
||||
state.Put("create_windows_password", true)
|
||||
|
||||
step := new(StepCreateWindowsPassword)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.CreateOrResetWindowsPasswordErr = errors.New("error")
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("winrm_password"); ok {
|
||||
t.Fatal("should NOT have instance name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateOrResetWindowsPassword_errorOnChannel(t *testing.T) {
|
||||
state := testState(t)
|
||||
|
||||
// Step is run after the instance is created so we will have an instance name set
|
||||
state.Put("instance_name", "mock_instance")
|
||||
state.Put("create_windows_password", true)
|
||||
|
||||
step := new(StepCreateWindowsPassword)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
errCh <- errors.New("error")
|
||||
|
||||
driver.CreateOrResetWindowsPasswordErrCh = errCh
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if _, ok := state.GetOk("winrm_password"); ok {
|
||||
t.Fatal("should NOT have instance name")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package googlecompute
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
)
|
||||
|
||||
// winrmConfig returns the WinRM configuration.
|
||||
func winrmConfig(state multistep.StateBag) (*communicator.WinRMConfig, error) {
|
||||
config := state.Get("config").(*Config)
|
||||
password := state.Get("winrm_password").(string)
|
||||
|
||||
return &communicator.WinRMConfig{
|
||||
Username: config.Comm.WinRMUser,
|
||||
Password: password,
|
||||
}, nil
|
||||
}
|
Loading…
Reference in New Issue