Last active
August 29, 2015 14:01
-
-
Save rasa/ae71926dcea1f38704ce to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- master..5248280.diff 2014-05-19 19:50:00.255845000 -0700 | |
+++ master..112ed70.diff 2014-05-19 19:50:20.368995400 -0700 | |
@@ -707,7 +707,7 @@ | |
NoPty: b.config.SSHSkipRequestPty, | |
diff --git a/common/step_connect_winrm.go b/common/step_connect_winrm.go | |
new file mode 100644 | |
-index 0000000..a91fa29 | |
+index 0000000..40f4040 | |
--- /dev/null | |
+++ b/common/step_connect_winrm.go | |
@@ -0,0 +1,130 @@ | |
@@ -823,7 +823,7 @@ | |
+ } | |
+ | |
+ log.Println("Attempting WinRM connection...") | |
-+ comm, err = winrm.New(address, s.WinRMUser, s.WinRMPassword) | |
++ comm, err = winrm.New(address, s.WinRMUser, s.WinRMPassword, s.WinRMWaitTimeout) | |
+ if err != nil { | |
+ log.Printf("WinRM handshake err: %s", err) | |
+ | |
@@ -841,20 +841,99 @@ | |
+ | |
+ return comm, nil | |
+} | |
+diff --git a/common/time/time.go b/common/time/time.go | |
+new file mode 100644 | |
+index 0000000..4ab8894 | |
+--- /dev/null | |
++++ b/common/time/time.go | |
+@@ -0,0 +1,30 @@ | |
++package time | |
++ | |
++import ( | |
++ "fmt" | |
++ "time" | |
++) | |
++ | |
++func ISO8601DurationString(d time.Duration) string { | |
++ // We're not supporting negative durations | |
++ if d.Seconds() <= 0 { | |
++ return "PT0S" | |
++ } | |
++ | |
++ hours := int(d.Hours()) | |
++ minutes := int(d.Minutes()) - (hours * 60) | |
++ seconds := int(d.Seconds()) - (hours*3600 + minutes*60) | |
++ | |
++ s := "PT" | |
++ if hours > 0 { | |
++ s = fmt.Sprintf("%s%dH", s, hours) | |
++ } | |
++ if minutes > 0 { | |
++ s = fmt.Sprintf("%s%dM", s, minutes) | |
++ } | |
++ if seconds > 0 { | |
++ s = fmt.Sprintf("%s%dS", s, seconds) | |
++ } | |
++ | |
++ return s | |
++} | |
+diff --git a/common/time/time_test.go b/common/time/time_test.go | |
+new file mode 100644 | |
+index 0000000..8fb86f0 | |
+--- /dev/null | |
++++ b/common/time/time_test.go | |
+@@ -0,0 +1,36 @@ | |
++package time | |
++ | |
++import ( | |
++ "testing" | |
++ "time" | |
++) | |
++ | |
++func TestISO8601DurationString(t *testing.T) { | |
++ // Test complex duration with hours, minutes, seconds | |
++ d := time.Duration(3701) * time.Second | |
++ s := ISO8601DurationString(d) | |
++ if s != "PT1H1M41S" { | |
++ t.Fatalf("bad ISO 8601 duration string: %s", s) | |
++ } | |
++ | |
++ // Test only minutes duration | |
++ d = time.Duration(20) * time.Minute | |
++ s = ISO8601DurationString(d) | |
++ if s != "PT20M" { | |
++ t.Fatalf("bad ISO 8601 duration string for 20M: %s", s) | |
++ } | |
++ | |
++ // Test only seconds | |
++ d = time.Duration(1) * time.Second | |
++ s = ISO8601DurationString(d) | |
++ if s != "PT1S" { | |
++ t.Fatalf("bad ISO 8601 duration string for 1S: %s", s) | |
++ } | |
++ | |
++ // Test negative duration (unsupported) | |
++ d = time.Duration(-1) * time.Second | |
++ s = ISO8601DurationString(d) | |
++ if s != "PT0S" { | |
++ t.Fatalf("bad ISO 8601 duration string for negative: %s", s) | |
++ } | |
++} | |
diff --git a/communicator/winrm/communicator.go b/communicator/winrm/communicator.go | |
new file mode 100644 | |
-index 0000000..ad70d05 | |
+index 0000000..e54262c | |
--- /dev/null | |
+++ b/communicator/winrm/communicator.go | |
-@@ -0,0 +1,106 @@ | |
+@@ -0,0 +1,243 @@ | |
+package winrm | |
+ | |
+import ( | |
-+ "errors" | |
++ isotime "github.com/mitchellh/packer/common/time" | |
+ "github.com/mitchellh/packer/packer" | |
+ "github.com/sneal/go-winrm" | |
+ "io" | |
+ "log" | |
++ "time" | |
+) | |
+ | |
+type comm struct { | |
@@ -862,48 +941,114 @@ | |
+ address string | |
+ user string | |
+ password string | |
++ timeout time.Duration | |
++} | |
++ | |
++type elevatedShellOptions struct { | |
++ Command string | |
++ User string | |
++ Password string | |
+} | |
+ | |
+// Creates a new packer.Communicator implementation over WinRM. | |
-+func New(address string, user string, password string) (result *comm, err error) { | |
-+ result = &comm{ | |
++// Called when Packer tries to connect to WinRM | |
++func New(address string, user string, password string, timeout time.Duration) (*comm, error) { | |
++ | |
++ // Create the WinRM client we use internally | |
++ params := winrm.DefaultParameters() | |
++ params.Timeout = isotime.ISO8601DurationString(timeout) | |
++ client := winrm.NewClientWithParameters(address, user, password, params) | |
++ | |
++ // Attempt to connect to the WinRM service | |
++ shell, err := client.CreateShell() | |
++ if err != nil { | |
++ return nil, err | |
++ } | |
++ | |
++ err = shell.Close() | |
++ if err != nil { | |
++ return nil, err | |
++ } | |
++ | |
++ return &comm{ | |
+ address: address, | |
+ user: user, | |
+ password: password, | |
++ timeout: timeout, | |
++ client: client, | |
++ }, nil | |
+ } | |
+ | |
-+ if err = result.reconnect(); err != nil { | |
-+ result = nil | |
-+ return | |
++func (c *comm) Start(cmd *packer.RemoteCmd) (err error) { | |
++ return c.StartElevated(cmd) | |
+ } | |
+ | |
-+ return | |
++func (c *comm) StartElevated(cmd *packer.RemoteCmd) (err error) { | |
++ // Wrap the command in scheduled task | |
++ tpl, err := packer.NewConfigTemplate() | |
++ if err != nil { | |
++ return err | |
+} | |
+ | |
-+func (c *comm) Start(cmd *packer.RemoteCmd) (err error) { | |
-+ session, err := c.newSession() | |
++ elevatedCmd, err := tpl.Process(ElevatedShellTemplate, &elevatedShellOptions{ | |
++ Command: cmd.Command, | |
++ User: c.user, | |
++ Password: c.password, | |
++ }) | |
+ if err != nil { | |
-+ return | |
++ return err | |
++ } | |
++ | |
++ return c.runCommand(winrm.Powershell(elevatedCmd), cmd) | |
+ } | |
+ | |
++func (c *comm) StartUnelevated(cmd *packer.RemoteCmd) (err error) { | |
++ return c.runCommand(cmd.Command, cmd) | |
++} | |
++ | |
++func (c *comm) runCommand(commandText string, cmd *packer.RemoteCmd) (err error) { | |
+ log.Printf("starting remote command: %s", cmd.Command) | |
-+ var sessionCmd *winrm.Command | |
-+ sessionCmd, err = session.Execute(cmd.Command, cmd.Stdout, cmd.Stderr) | |
++ | |
++ // Create a new shell process on the guest | |
++ shell, err := c.client.CreateShell() | |
++ if err != nil { | |
++ log.Printf("error creating shell, retrying once more: %s", err) | |
++ shell, err = c.client.CreateShell() | |
++ if err != nil { | |
++ log.Printf("error creating shell, giving up: %s", err) | |
++ return err | |
++ } | |
++ } | |
++ | |
++ // Execute the command | |
++ var winrmCmd *winrm.Command | |
++ winrmCmd, err = shell.Execute(commandText, cmd.Stdout, cmd.Stderr) | |
+ if err != nil { | |
++ log.Printf("error executing command: %s", err) | |
+ return err | |
+ } | |
+ | |
-+ // Start a goroutine to wait for the session to end and set the | |
++ // Start a goroutine to wait for the shell to end and set the | |
+ // exit boolean and status. | |
+ go func() { | |
-+ defer session.Close() | |
++ | |
++ defer func() { | |
++ err = winrmCmd.Close() | |
++ if err != nil { | |
++ log.Printf("winrm command failed to close: %s", err) | |
++ } | |
++ err = shell.Close() | |
++ if err != nil { | |
++ log.Printf("shell failed to close: %s", err) | |
++ } | |
++ }() | |
+ | |
+ // Block until done | |
-+ sessionCmd.Wait() | |
++ winrmCmd.Wait() | |
+ | |
+ // Report exit status and trigger done | |
-+ exitStatus := sessionCmd.ExitCode() | |
-+ log.Printf("remote command exited with '%d': %s", exitStatus, cmd.Command) | |
++ exitStatus := winrmCmd.ExitCode() | |
++ log.Printf("remote command exited with '%d'", exitStatus) | |
+ cmd.SetExited(exitStatus) | |
+ }() | |
+ | |
@@ -928,37 +1073,107 @@ | |
+ panic("Download not implemented yet") | |
+} | |
+ | |
-+func (c *comm) reconnect() (err error) { | |
-+ c.client = winrm.NewClient(c.address, c.user, c.password) | |
-+ _, err = c.client.CreateShell() | |
-+ return err | |
-+} | |
++const ElevatedShellTemplate = ` | |
++$command = "{{.Command}}" | |
++$user = '{{.User}}' | |
++$password = '{{.Password}}' | |
++ | |
++$task_name = "WinRM_Elevated_Shell" | |
++$out_file = "$env:SystemRoot\Temp\WinRM_Elevated_Shell.log" | |
++ | |
++if (Test-Path $out_file) { | |
++ del $out_file | |
++} | |
++ | |
++$task_xml = @' | |
++<?xml version="1.0" encoding="UTF-16"?> | |
++<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> | |
++ <Principals> | |
++ <Principal id="Author"> | |
++ <UserId>{user}</UserId> | |
++ <LogonType>Password</LogonType> | |
++ <RunLevel>HighestAvailable</RunLevel> | |
++ </Principal> | |
++ </Principals> | |
++ <Settings> | |
++ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> | |
++ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> | |
++ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries> | |
++ <AllowHardTerminate>true</AllowHardTerminate> | |
++ <StartWhenAvailable>false</StartWhenAvailable> | |
++ <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable> | |
++ <IdleSettings> | |
++ <StopOnIdleEnd>true</StopOnIdleEnd> | |
++ <RestartOnIdle>false</RestartOnIdle> | |
++ </IdleSettings> | |
++ <AllowStartOnDemand>true</AllowStartOnDemand> | |
++ <Enabled>true</Enabled> | |
++ <Hidden>false</Hidden> | |
++ <RunOnlyIfIdle>false</RunOnlyIfIdle> | |
++ <WakeToRun>false</WakeToRun> | |
++ <ExecutionTimeLimit>PT2H</ExecutionTimeLimit> | |
++ <Priority>4</Priority> | |
++ </Settings> | |
++ <Actions Context="Author"> | |
++ <Exec> | |
++ <Command>cmd</Command> | |
++ <Arguments>{arguments}</Arguments> | |
++ </Exec> | |
++ </Actions> | |
++</Task> | |
++'@ | |
++ | |
++$bytes = [System.Text.Encoding]::Unicode.GetBytes($command) | |
++$encoded_command = [Convert]::ToBase64String($bytes) | |
++$arguments = "/c powershell.exe -EncodedCommand $encoded_command > $out_file 2>&1" | |
++ | |
++$task_xml = $task_xml.Replace("{arguments}", $arguments) | |
++$task_xml = $task_xml.Replace("{user}", $user) | |
++ | |
++$schedule = New-Object -ComObject "Schedule.Service" | |
++$schedule.Connect() | |
++$task = $schedule.NewTask($null) | |
++$task.XmlText = $task_xml | |
++$folder = $schedule.GetFolder("\") | |
++$folder.RegisterTaskDefinition($task_name, $task, 6, $user, $password, 1, $null) | Out-Null | |
++ | |
++$registered_task = $folder.GetTask("\$task_name") | |
++$registered_task.Run($null) | Out-Null | |
++ | |
++$timeout = 10 | |
++$sec = 0 | |
++while ( (!($registered_task.state -eq 4)) -and ($sec -lt $timeout) ) { | |
++ Start-Sleep -s 1 | |
++ $sec++ | |
++} | |
++ | |
++function SlurpOutput($out_file, $cur_line) { | |
++ if (Test-Path $out_file) { | |
++ get-content $out_file | select -skip $cur_line | ForEach { | |
++ $cur_line += 1 | |
++ Write-Host "$_" | |
++ } | |
++ } | |
++ return $cur_line | |
++} | |
++ | |
++$cur_line = 0 | |
++do { | |
++ Start-Sleep -m 100 | |
++ $cur_line = SlurpOutput $out_file $cur_line | |
++} while (!($registered_task.state -eq 3)) | |
+ | |
-+func (c *comm) newSession() (session *winrm.Shell, err error) { | |
-+ log.Println("opening new WinRM session") | |
-+ if c.client == nil { | |
-+ err = errors.New("client not available") | |
-+ } else { | |
-+ session, err = c.client.CreateShell() | |
-+ } | |
++$exit_code = $registered_task.LastTaskResult | |
++[System.Runtime.Interopservices.Marshal]::ReleaseComObject($schedule) | Out-Null | |
+ | |
-+ if err != nil { | |
-+ log.Printf("WinRM session open error: '%s', attempting reconnect", err) | |
-+ if err := c.reconnect(); err != nil { | |
-+ return nil, err | |
-+ } | |
-+ | |
-+ return c.client.CreateShell() | |
-+ } | |
-+ | |
-+ return session, nil | |
-+} | |
++exit $exit_code | |
++` | |
diff --git a/communicator/winrm/communicator_test.go b/communicator/winrm/communicator_test.go | |
new file mode 100644 | |
-index 0000000..961c002 | |
+index 0000000..622d322 | |
--- /dev/null | |
+++ b/communicator/winrm/communicator_test.go | |
-@@ -0,0 +1,77 @@ | |
+@@ -0,0 +1,120 @@ | |
+// +build !race | |
+ | |
+package winrm | |
@@ -969,6 +1184,7 @@ | |
+ "github.com/mitchellh/packer/packer" | |
+ "os" | |
+ "testing" | |
++ "time" | |
+) | |
+ | |
+func TestStart(t *testing.T) { | |
@@ -976,7 +1192,7 @@ | |
+ // You can comment this line out temporarily during development | |
+ t.Skip() | |
+ | |
-+ comm, err := New("localhost:5985", "vagrant", "vagrant") | |
++ comm, err := New("localhost:5985", "vagrant", "vagrant", time.Duration(1) * time.Minute) | |
+ if err != nil { | |
+ t.Fatalf("error connecting to WinRM: %s", err) | |
+ } | |
@@ -996,7 +1212,49 @@ | |
+ | |
+ fmt.Println(outWriter.String()) | |
+ fmt.Println(errWriter.String()) | |
-+ fmt.Println(err) | |
++ | |
++ if err != nil { | |
++ t.Fatalf("error running cmd: %s", err) | |
++ } | |
++ | |
++ if cmd.ExitStatus != 0 { | |
++ t.Fatalf("exit status was non-zero: %d", cmd.ExitStatus) | |
++ } | |
++} | |
++ | |
++func TestStartElevated(t *testing.T) { | |
++ // This test hits an already running Windows VM | |
++ // You can comment this line out temporarily during development | |
++ t.Skip() | |
++ | |
++ comm, err := New("localhost:5985", "vagrant", "vagrant", time.Duration(1) * time.Minute) | |
++ if err != nil { | |
++ t.Fatalf("error connecting to WinRM: %s", err) | |
++ } | |
++ | |
++ var cmd packer.RemoteCmd | |
++ var outWriter, errWriter bytes.Buffer | |
++ | |
++ cmd.Command = "(New-Object System.Net.WebClient).DownloadFile('http://www.getchef.com/chef/install.msi', 'C:/Windows/Temp/chef.msi');Start-Process 'msiexec' -ArgumentList '/qb /i C:\\Windows\\Temp\\chef.msi' -NoNewWindow -Wait" | |
++ cmd.Stdout = &outWriter | |
++ cmd.Stderr = &errWriter | |
++ | |
++ err = comm.StartElevated(&cmd) | |
++ if err != nil { | |
++ t.Fatalf("error starting cmd: %s", err) | |
++ } | |
++ cmd.Wait() | |
++ | |
++ fmt.Println(outWriter.String()) | |
++ fmt.Println(errWriter.String()) | |
++ | |
++ if err != nil { | |
++ t.Fatalf("error running cmd: %s", err) | |
++ } | |
++ | |
++ if cmd.ExitStatus != 0 { | |
++ t.Fatalf("exit status was non-zero: %d", cmd.ExitStatus) | |
++ } | |
+} | |
+ | |
+func TestUpload(t *testing.T) { | |
@@ -1004,12 +1262,12 @@ | |
+ // You can comment this line out temporarily during development | |
+ t.Skip() | |
+ | |
-+ comm, err := New("localhost:5985", "vagrant", "vagrant") | |
++ comm, err := New("localhost:5985", "vagrant", "vagrant", time.Duration(1) * time.Minute) | |
+ if err != nil { | |
+ t.Fatalf("error connecting to WinRM: %s", err) | |
+ } | |
+ | |
-+ f, err := os.Open("/Users/sneal/Downloads/photo1.jpg") | |
++ f, err := os.Open("~/Downloads/photo1.jpg") | |
+ if err != nil { | |
+ t.Fatalf("error opening file: %s", err) | |
+ } | |
@@ -1026,22 +1284,22 @@ | |
+ // You can comment this line out temporarily during development | |
+ t.Skip() | |
+ | |
-+ comm, err := New("localhost:5985", "vagrant", "vagrant") | |
++ comm, err := New("localhost:5985", "vagrant", "vagrant", time.Duration(1) * time.Minute) | |
+ if err != nil { | |
+ t.Fatalf("error connecting to WinRM: %s", err) | |
+ } | |
+ | |
-+ err = comm.UploadDir("c:\\winrm-upload", "/Users/sneal/src/vagrant-windows/lib", nil) | |
++ err = comm.UploadDir("c:\\src\\chef-repo", "~/src/chef-repo", nil) | |
+ if err != nil { | |
+ t.Fatalf("error uploading dir: %s", err) | |
+ } | |
+} | |
diff --git a/communicator/winrm/file_manager.go b/communicator/winrm/file_manager.go | |
new file mode 100644 | |
-index 0000000..bd79181 | |
+index 0000000..32f4896 | |
--- /dev/null | |
+++ b/communicator/winrm/file_manager.go | |
-@@ -0,0 +1,140 @@ | |
+@@ -0,0 +1,141 @@ | |
+package winrm | |
+ | |
+import ( | |
@@ -1103,17 +1361,18 @@ | |
+ } | |
+ else { | |
+ $dest_dir = ([System.IO.Path]::GetDirectoryName($dest_file_path)) | |
-+ if ($dest_dir.length -gt 3) { | |
-+ New-Item -ItemType directory -Force -Path $dest_dir | |
-+ } | |
++ New-Item -ItemType directory -Force -ErrorAction SilentlyContinue -Path $dest_dir | |
+ } | |
+ | |
++ if (Test-Path $tmp_file_path) { | |
+ $base64_lines = Get-Content $tmp_file_path | |
+ $base64_string = [string]::join("",$base64_lines) | |
+ $bytes = [System.Convert]::FromBase64String($base64_string) | |
+ [System.IO.File]::WriteAllBytes($dest_file_path, $bytes) | |
-+ | |
+ Remove-Item $tmp_file_path -Force -ErrorAction SilentlyContinue | |
++ } else { | |
++ echo $null > $dest_file_path | |
++ } | |
+ `, guestFilePath, dst))) | |
+ | |
+ return err | |
@@ -1142,7 +1401,7 @@ | |
+ Command: cmd, | |
+ } | |
+ | |
-+ err := f.comm.Start(remoteCmd) | |
++ err := f.comm.StartUnelevated(remoteCmd) | |
+ if err != nil { | |
+ return err | |
+ } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment