Extend upload and subsequent 'dot sourcing' of env vars to std PS command

* Wrap funcs to flatten and upload env vars with new func prepareEnvVars.
  While the wrapped funcs could be combined, keeping them separate
  simplifies testing.
* Configure/refactor std and elevated PS to use new funcs to prepare,
  upload and dot source env vars.
* Dot sourcing the env vars in this way avoids the need to embed them
  directly in the command string. This avoids the need to escape the env
  vars to ensure the command string is correctly parsed.
* Characters within the env vars that are special to PS (such as $'s
  and backticks) will still need to be escaped to allow them to be
  correctly interpreted by PS.
* The std and elevated PS commands now inject env vars into the remote
  env via the same mechanism. This ensures consistent behaviour across the
  two command types.

Fixes #5471
This commit is contained in:
DanHam 2017-10-25 13:50:58 +01:00
parent fe4d4648e6
commit 5949bc91c4
No known key found for this signature in database
GPG Key ID: 58E79AEDD6AA987E
1 changed files with 37 additions and 15 deletions

View File

@ -113,7 +113,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
if p.config.EnvVarFormat == "" {
p.config.EnvVarFormat = `$env:%s=\"%s\"; `
p.config.EnvVarFormat = `$env:%s="%s"; `
}
if p.config.ElevatedEnvVarFormat == "" {
@ -121,7 +121,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
if p.config.ExecuteCommand == "" {
p.config.ExecuteCommand = `powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode }"`
p.config.ExecuteCommand = `powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}';exit $LastExitCode }"`
}
if p.config.ElevatedExecuteCommand == "" {
@ -331,6 +331,19 @@ func (p *Provisioner) retryable(f func() error) error {
}
}
// Enviroment 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) {
// 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)
if err != nil {
return "", err
}
return
}
func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) {
flattened = ""
envVars := make(map[string]string)
@ -367,6 +380,19 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) {
return
}
func (p *Provisioner) uploadEnvVars(flattenedEnvVars string) (envVarPath 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)
if err != nil {
return "", fmt.Errorf("Error uploading ps script containing env vars: %s", err)
}
return
}
func (p *Provisioner) createCommandText() (command string, err error) {
// Return the interpolated command
if p.config.ElevatedUser == "" {
@ -377,12 +403,15 @@ func (p *Provisioner) createCommandText() (command string, err error) {
}
func (p *Provisioner) createCommandTextNonPrivileged() (command string, err error) {
// Create environment variables to set before executing the command
flattenedEnvVars := p.createFlattenedEnvVars(false)
// Prepare everything needed to enable the required env vars within the remote environment
envVarPath, err := p.prepareEnvVars(false)
if err != nil {
return "", err
}
p.config.ctx.Data = &ExecuteCommandTemplate{
Vars: flattenedEnvVars,
Path: p.config.RemotePath,
Vars: envVarPath,
}
command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
@ -395,17 +424,10 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro
}
func (p *Provisioner) createCommandTextPrivileged() (command string, err error) {
// Can't double escape the env vars, lets create shiny new ones
flattenedEnvVars := p.createFlattenedEnvVars(true)
// Need to create a mini ps1 script containing all of the environment variables we want;
// we'll be dot-sourcing this later
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)
// Prepare everything needed to enable the required env vars within the remote environment
envVarPath, err := p.prepareEnvVars(false)
if err != nil {
return "", fmt.Errorf("Error preparing elevated powershell script: %s", err)
return "", err
}
p.config.ctx.Data = &ExecuteCommandTemplate{