mirror of
https://github.com/hashicorp/packer.git
synced 2026-05-28 04:35:38 -04:00
Merge pull request #6160 from DanHam/fix-powershell-ssh
Fixes for PowerShell using ssh communicator
This commit is contained in:
commit
52df315ca9
3 changed files with 95 additions and 36 deletions
|
|
@ -57,6 +57,11 @@ type Config struct {
|
|||
// This should be set to a writable file that is in a pre-existing directory.
|
||||
RemotePath string `mapstructure:"remote_path"`
|
||||
|
||||
// The remote path where the file containing the environment variables
|
||||
// will be uploaded to. This should be set to a writable file that is
|
||||
// in a pre-existing directory.
|
||||
RemoteEnvVarPath string `mapstructure:"remote_env_var_path"`
|
||||
|
||||
// The command used to execute the script. The '{{ .Path }}' variable
|
||||
// should be used to specify where the script goes, {{ .Vars }}
|
||||
// can be used to inject the environment_vars into the environment.
|
||||
|
|
@ -159,6 +164,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
|||
p.config.RemotePath = fmt.Sprintf(`c:/Windows/Temp/script-%s.ps1`, uuid)
|
||||
}
|
||||
|
||||
if p.config.RemoteEnvVarPath == "" {
|
||||
uuid := uuid.TimeOrderedUUID()
|
||||
p.config.RemoteEnvVarPath = fmt.Sprintf(`c:/Windows/Temp/packer-ps-env-vars-%s.ps1`, uuid)
|
||||
}
|
||||
|
||||
if p.config.Scripts == nil {
|
||||
p.config.Scripts = make([]string, 0)
|
||||
}
|
||||
|
|
@ -351,13 +361,13 @@ func (p *Provisioner) retryable(f func() error) error {
|
|||
|
||||
// Environment variables required within the remote environment are uploaded within a PS script and
|
||||
// then enabled by 'dot sourcing' the script immediately prior to execution of the main command
|
||||
func (p *Provisioner) prepareEnvVars(elevated bool) (envVarPath string, err error) {
|
||||
func (p *Provisioner) prepareEnvVars(elevated bool) (err error) {
|
||||
// Collate all required env vars into a plain string with required formatting applied
|
||||
flattenedEnvVars := p.createFlattenedEnvVars(elevated)
|
||||
// Create a powershell script on the target build fs containing the flattened env vars
|
||||
envVarPath, err = p.uploadEnvVars(flattenedEnvVars)
|
||||
err = p.uploadEnvVars(flattenedEnvVars)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -413,15 +423,13 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) {
|
|||
return
|
||||
}
|
||||
|
||||
func (p *Provisioner) uploadEnvVars(flattenedEnvVars string) (envVarPath string, err error) {
|
||||
func (p *Provisioner) uploadEnvVars(flattenedEnvVars string) (err error) {
|
||||
// Upload all env vars to a powershell script on the target build file system
|
||||
envVarReader := strings.NewReader(flattenedEnvVars)
|
||||
uuid := uuid.TimeOrderedUUID()
|
||||
envVarPath = fmt.Sprintf(`${env:SYSTEMROOT}/Temp/packer-env-vars-%s.ps1`, uuid)
|
||||
log.Printf("Uploading env vars to %s", envVarPath)
|
||||
err = p.communicator.Upload(envVarPath, envVarReader, nil)
|
||||
log.Printf("Uploading env vars to %s", p.config.RemoteEnvVarPath)
|
||||
err = p.communicator.Upload(p.config.RemoteEnvVarPath, envVarReader, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error uploading ps script containing env vars: %s", err)
|
||||
return fmt.Errorf("Error uploading ps script containing env vars: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -437,14 +445,14 @@ func (p *Provisioner) createCommandText() (command string, err error) {
|
|||
|
||||
func (p *Provisioner) createCommandTextNonPrivileged() (command string, err error) {
|
||||
// Prepare everything needed to enable the required env vars within the remote environment
|
||||
envVarPath, err := p.prepareEnvVars(false)
|
||||
err = p.prepareEnvVars(false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
p.config.ctx.Data = &ExecuteCommandTemplate{
|
||||
Path: p.config.RemotePath,
|
||||
Vars: envVarPath,
|
||||
Vars: p.config.RemoteEnvVarPath,
|
||||
WinRMPassword: getWinRMPassword(p.config.PackerBuildName),
|
||||
}
|
||||
command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
|
||||
|
|
@ -464,14 +472,14 @@ func getWinRMPassword(buildName string) string {
|
|||
|
||||
func (p *Provisioner) createCommandTextPrivileged() (command string, err error) {
|
||||
// Prepare everything needed to enable the required env vars within the remote environment
|
||||
envVarPath, err := p.prepareEnvVars(true)
|
||||
err = p.prepareEnvVars(true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
p.config.ctx.Data = &ExecuteCommandTemplate{
|
||||
Path: p.config.RemotePath,
|
||||
Vars: envVarPath,
|
||||
Vars: p.config.RemoteEnvVarPath,
|
||||
WinRMPassword: getWinRMPassword(p.config.PackerBuildName),
|
||||
}
|
||||
command, err = interpolate.Render(p.config.ElevatedExecuteCommand, &p.config.ctx)
|
||||
|
|
@ -557,14 +565,11 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin
|
|||
return "", err
|
||||
}
|
||||
uuid := uuid.TimeOrderedUUID()
|
||||
path := fmt.Sprintf(`${env:TEMP}/packer-elevated-shell-%s.ps1`, uuid)
|
||||
path := fmt.Sprintf(`C:/Windows/Temp/packer-elevated-shell-%s.ps1`, uuid)
|
||||
log.Printf("Uploading elevated shell wrapper for command [%s] to [%s]", command, path)
|
||||
err = p.communicator.Upload(path, &buffer, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error preparing elevated powershell script: %s", err)
|
||||
}
|
||||
|
||||
// CMD formatted Path required for this op
|
||||
path = fmt.Sprintf("%s-%s.ps1", "%TEMP%/packer-elevated-shell", uuid)
|
||||
return path, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -414,7 +414,7 @@ func TestProvisionerProvision_Inline(t *testing.T) {
|
|||
}
|
||||
|
||||
cmd := comm.StartCmd.Command
|
||||
re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. \${env:SYSTEMROOT}/Temp/packer-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/inlineScript.ps1';exit \$LastExitCode }"`)
|
||||
re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/inlineScript.ps1';exit \$LastExitCode }"`)
|
||||
matched := re.MatchString(cmd)
|
||||
if !matched {
|
||||
t.Fatalf("Got unexpected command: %s", cmd)
|
||||
|
|
@ -434,7 +434,7 @@ func TestProvisionerProvision_Inline(t *testing.T) {
|
|||
}
|
||||
|
||||
cmd = comm.StartCmd.Command
|
||||
re = regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. \${env:SYSTEMROOT}/Temp/packer-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/inlineScript.ps1';exit \$LastExitCode }"`)
|
||||
re = regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/inlineScript.ps1';exit \$LastExitCode }"`)
|
||||
matched = re.MatchString(cmd)
|
||||
if !matched {
|
||||
t.Fatalf("Got unexpected command: %s", cmd)
|
||||
|
|
@ -461,7 +461,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) {
|
|||
}
|
||||
|
||||
cmd := comm.StartCmd.Command
|
||||
re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. \${env:SYSTEMROOT}/Temp/packer-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1';exit \$LastExitCode }"`)
|
||||
re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1';exit \$LastExitCode }"`)
|
||||
matched := re.MatchString(cmd)
|
||||
if !matched {
|
||||
t.Fatalf("Got unexpected command: %s", cmd)
|
||||
|
|
@ -495,7 +495,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) {
|
|||
}
|
||||
|
||||
cmd := comm.StartCmd.Command
|
||||
re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. \${env:SYSTEMROOT}/Temp/packer-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1';exit \$LastExitCode }"`)
|
||||
re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1';exit \$LastExitCode }"`)
|
||||
matched := re.MatchString(cmd)
|
||||
if !matched {
|
||||
t.Fatalf("Got unexpected command: %s", cmd)
|
||||
|
|
@ -612,7 +612,7 @@ func TestProvision_createCommandText(t *testing.T) {
|
|||
// Non-elevated
|
||||
cmd, _ := p.createCommandText()
|
||||
|
||||
re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. \${env:SYSTEMROOT}/Temp/packer-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1';exit \$LastExitCode }"`)
|
||||
re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1';exit \$LastExitCode }"`)
|
||||
matched := re.MatchString(cmd)
|
||||
if !matched {
|
||||
t.Fatalf("Got unexpected command: %s", cmd)
|
||||
|
|
@ -622,7 +622,7 @@ func TestProvision_createCommandText(t *testing.T) {
|
|||
p.config.ElevatedUser = "vagrant"
|
||||
p.config.ElevatedPassword = "vagrant"
|
||||
cmd, _ = p.createCommandText()
|
||||
re = regexp.MustCompile(`powershell -executionpolicy bypass -file "%TEMP%/packer-elevated-shell-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1"`)
|
||||
re = regexp.MustCompile(`powershell -executionpolicy bypass -file "C:/Windows/Temp/packer-elevated-shell-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1"`)
|
||||
matched = re.MatchString(cmd)
|
||||
if !matched {
|
||||
t.Fatalf("Got unexpected elevated command: %s", cmd)
|
||||
|
|
@ -636,7 +636,7 @@ func TestProvision_uploadEnvVars(t *testing.T) {
|
|||
|
||||
flattenedEnvVars := `$env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild";`
|
||||
|
||||
envVarPath, err := p.uploadEnvVars(flattenedEnvVars)
|
||||
err := p.uploadEnvVars(flattenedEnvVars)
|
||||
if err != nil {
|
||||
t.Fatalf("Did not expect error: %s", err.Error())
|
||||
}
|
||||
|
|
@ -644,12 +644,6 @@ func TestProvision_uploadEnvVars(t *testing.T) {
|
|||
if comm.UploadCalled != true {
|
||||
t.Fatalf("Failed to upload env var file")
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`\${env:SYSTEMROOT}/Temp/packer-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1`)
|
||||
matched := re.MatchString(envVarPath)
|
||||
if !matched {
|
||||
t.Fatalf("Got unexpected path for env var file: %s", envVarPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvision_generateElevatedShellRunner(t *testing.T) {
|
||||
|
|
@ -670,7 +664,7 @@ func TestProvision_generateElevatedShellRunner(t *testing.T) {
|
|||
t.Fatalf("Should have uploaded file")
|
||||
}
|
||||
|
||||
matched, _ := regexp.MatchString("%TEMP%(.{1})packer-elevated-shell.*", path)
|
||||
matched, _ := regexp.MatchString("C:/Windows/Temp/packer-elevated-shell.*", path)
|
||||
if !matched {
|
||||
t.Fatalf("Got unexpected file: %s", path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,11 @@ sidebar_current: 'docs-provisioners-powershell'
|
|||
Type: `powershell`
|
||||
|
||||
The PowerShell Packer provisioner runs PowerShell scripts on Windows machines.
|
||||
It assumes that the communicator in use is WinRM.
|
||||
It assumes that the communicator in use is WinRM. However, the provisioner
|
||||
can work equally well (with a few caveats) when combined with the SSH
|
||||
communicator. See the [section
|
||||
below](/docs/provisioners/powershell.html#combining-the-powershell-provisioner-with-the-ssh-communicator)
|
||||
for details.
|
||||
|
||||
## Basic Example
|
||||
|
||||
|
|
@ -97,7 +101,9 @@ Optional parameters:
|
|||
template](/docs/templates/engine.html). There are two
|
||||
available variables: `Path`, which is the path to the script to run, and
|
||||
`Vars`, which is the location of a temp file containing the list of
|
||||
`environment_vars`, if configured.
|
||||
`environment_vars`. The value of both `Path` and `Vars` can be
|
||||
manually configured by setting the values for `remote_path` and
|
||||
`remote_env_var_path` respectively.
|
||||
|
||||
- `elevated_user` and `elevated_password` (string) - If specified, the
|
||||
PowerShell script will be run with elevated privileges using the given
|
||||
|
|
@ -111,9 +117,28 @@ Optional parameters:
|
|||
"elevated_password": "{{.WinRMPassword}}",
|
||||
```
|
||||
|
||||
- `remote_path` (string) - The path where the script will be uploaded to in
|
||||
the machine. This defaults to "c:/Windows/Temp/script.ps1". This value must
|
||||
be a writable location and any parent directories must already exist.
|
||||
- `remote_path` (string) - The path where the PowerShell script will be
|
||||
uploaded to within the target build machine. This defaults to
|
||||
`C:/Windows/Temp/script-UUID.ps1` where UUID is replaced with a
|
||||
dynamically generated string that uniquely identifies the script.
|
||||
|
||||
This setting allows users to override the default upload location. The
|
||||
value must be a writable location and any parent directories must
|
||||
already exist.
|
||||
|
||||
- `remote_env_var_path` (string) - Environment variables required within
|
||||
the remote environment are uploaded within a PowerShell script and then
|
||||
enabled by 'dot sourcing' the script immediately prior to execution of
|
||||
the main command or script.
|
||||
|
||||
The path the environment variables script will be uploaded to defaults to
|
||||
`C:/Windows/Temp/packer-ps-env-vars-UUID.ps1` where UUID is replaced
|
||||
with a dynamically generated string that uniquely identifies the
|
||||
script.
|
||||
|
||||
This setting allows users to override the location the environment
|
||||
variable script is uploaded to. The value must be a writable location
|
||||
and any parent directories must already exist.
|
||||
|
||||
- `start_retry_timeout` (string) - The amount of time to attempt to *start*
|
||||
the remote process. By default this is "5m" or 5 minutes. This setting
|
||||
|
|
@ -147,6 +172,41 @@ commonly useful environmental variables:
|
|||
slower speeds using the default file provisioner. A file provisioner using
|
||||
the `winrm` communicator may experience these types of difficulties.
|
||||
|
||||
## Combining the PowerShell Provisioner with the SSH Communicator
|
||||
|
||||
The good news first. If you are using the
|
||||
[Microsoft port of OpenSSH](https://github.com/PowerShell/Win32-OpenSSH/wiki)
|
||||
then the provisioner should just work as expected - no extra configuration
|
||||
effort is required.
|
||||
|
||||
Now the caveats. If you are using an alternative configuration, and your SSH
|
||||
connection lands you in a *nix shell on the remote host, then you will most
|
||||
likely need to manually set the `execute_command`; The default
|
||||
`execute_command` used by Packer will not work for you.
|
||||
When configuring the command you will need to ensure that any dollar signs
|
||||
or other characters that may be incorrectly interpreted by the remote shell
|
||||
are escaped accordingly.
|
||||
|
||||
The following example shows how the standard `execute_command` can be
|
||||
reconfigured to work on a remote system with
|
||||
[Cygwin/OpenSSH](https://cygwin.com/) installed.
|
||||
The `execute_command` has each dollar sign backslash escaped so that it is
|
||||
not interpreted by the remote Bash shell - Bash being the default shell for
|
||||
Cygwin environments.
|
||||
|
||||
```json
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "powershell",
|
||||
"execute_command": "powershell -executionpolicy bypass \"& { if (Test-Path variable:global:ProgressPreference){\\$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}'; exit \\$LastExitCode }\"",
|
||||
"inline": [
|
||||
"Write-Host \"Hello from PowerShell\"",
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
## Packer's Handling of Characters Special to PowerShell
|
||||
|
||||
The escape character in PowerShell is the `backtick`, also sometimes
|
||||
|
|
|
|||
Loading…
Reference in a new issue