Merge pull request #7078 from aspectcapital/issue-5478
Elevated support for puppet-* & chef-client provisioners
This commit is contained in:
commit
70c6fcb824
|
@ -34,3 +34,15 @@ func (t *MockProvisioner) Provision(ui Ui, comm Communicator) error {
|
||||||
func (t *MockProvisioner) Cancel() {
|
func (t *MockProvisioner) Cancel() {
|
||||||
t.CancelCalled = true
|
t.CancelCalled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *MockProvisioner) Communicator() Communicator {
|
||||||
|
return t.ProvCommunicator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MockProvisioner) ElevatedUser() string {
|
||||||
|
return "user"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MockProvisioner) ElevatedPassword() string {
|
||||||
|
return "password"
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/packer/common"
|
"github.com/hashicorp/packer/common"
|
||||||
"github.com/hashicorp/packer/common/uuid"
|
"github.com/hashicorp/packer/common/uuid"
|
||||||
|
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||||
"github.com/hashicorp/packer/helper/config"
|
"github.com/hashicorp/packer/helper/config"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/hashicorp/packer/provisioner"
|
"github.com/hashicorp/packer/provisioner"
|
||||||
|
@ -50,6 +51,8 @@ type Config struct {
|
||||||
ChefEnvironment string `mapstructure:"chef_environment"`
|
ChefEnvironment string `mapstructure:"chef_environment"`
|
||||||
ClientKey string `mapstructure:"client_key"`
|
ClientKey string `mapstructure:"client_key"`
|
||||||
ConfigTemplate string `mapstructure:"config_template"`
|
ConfigTemplate string `mapstructure:"config_template"`
|
||||||
|
ElevatedUser string `mapstructure:"elevated_user"`
|
||||||
|
ElevatedPassword string `mapstructure:"elevated_password"`
|
||||||
EncryptedDataBagSecretPath string `mapstructure:"encrypted_data_bag_secret_path"`
|
EncryptedDataBagSecretPath string `mapstructure:"encrypted_data_bag_secret_path"`
|
||||||
ExecuteCommand string `mapstructure:"execute_command"`
|
ExecuteCommand string `mapstructure:"execute_command"`
|
||||||
GuestOSType string `mapstructure:"guest_os_type"`
|
GuestOSType string `mapstructure:"guest_os_type"`
|
||||||
|
@ -76,6 +79,7 @@ type Config struct {
|
||||||
|
|
||||||
type Provisioner struct {
|
type Provisioner struct {
|
||||||
config Config
|
config Config
|
||||||
|
communicator packer.Communicator
|
||||||
guestOSTypeConfig guestOSTypeConfig
|
guestOSTypeConfig guestOSTypeConfig
|
||||||
guestCommands *provisioner.GuestCommands
|
guestCommands *provisioner.GuestCommands
|
||||||
}
|
}
|
||||||
|
@ -94,6 +98,10 @@ type ConfigTemplate struct {
|
||||||
ValidationKeyPath string
|
ValidationKeyPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EnvVarsTemplate struct {
|
||||||
|
WinRMPassword string
|
||||||
|
}
|
||||||
|
|
||||||
type ExecuteTemplate struct {
|
type ExecuteTemplate struct {
|
||||||
ConfigPath string
|
ConfigPath string
|
||||||
JsonPath string
|
JsonPath string
|
||||||
|
@ -111,6 +119,12 @@ type KnifeTemplate struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||||
|
// Create passthrough for winrm password so we can fill it in once we know
|
||||||
|
// it
|
||||||
|
p.config.ctx.Data = &EnvVarsTemplate{
|
||||||
|
WinRMPassword: `{{.WinRMPassword}}`,
|
||||||
|
}
|
||||||
|
|
||||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &p.config.ctx,
|
InterpolateContext: &p.config.ctx,
|
||||||
|
@ -221,6 +235,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||||
|
|
||||||
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||||
|
|
||||||
|
p.communicator = comm
|
||||||
|
|
||||||
nodeName := p.config.NodeName
|
nodeName := p.config.NodeName
|
||||||
if nodeName == "" {
|
if nodeName == "" {
|
||||||
nodeName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
nodeName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||||
|
@ -551,6 +567,13 @@ func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.config.ElevatedUser != "" {
|
||||||
|
command, err = provisioner.GenerateElevatedRunner(command, p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Executing Chef: %s", command))
|
ui.Message(fmt.Sprintf("Executing Chef: %s", command))
|
||||||
|
|
||||||
cmd := &packer.RemoteCmd{
|
cmd := &packer.RemoteCmd{
|
||||||
|
@ -676,6 +699,31 @@ func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getWinRMPassword(buildName string) string {
|
||||||
|
winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName)
|
||||||
|
packer.LogSecretFilter.Set(winRMPass)
|
||||||
|
return winRMPass
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provisioner) Communicator() packer.Communicator {
|
||||||
|
return p.communicator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provisioner) ElevatedUser() string {
|
||||||
|
return p.config.ElevatedUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provisioner) ElevatedPassword() string {
|
||||||
|
// Replace ElevatedPassword for winrm users who used this feature
|
||||||
|
p.config.ctx.Data = &EnvVarsTemplate{
|
||||||
|
WinRMPassword: getWinRMPassword(p.config.PackerBuildName),
|
||||||
|
}
|
||||||
|
|
||||||
|
elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx)
|
||||||
|
|
||||||
|
return elevatedPassword
|
||||||
|
}
|
||||||
|
|
||||||
var DefaultConfigTemplate = `
|
var DefaultConfigTemplate = `
|
||||||
log_level :info
|
log_level :info
|
||||||
log_location STDOUT
|
log_location STDOUT
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
package provisioner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/common/uuid"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ElevatedProvisioner interface {
|
||||||
|
Communicator() packer.Communicator
|
||||||
|
ElevatedUser() string
|
||||||
|
ElevatedPassword() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type elevatedOptions struct {
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
TaskName string
|
||||||
|
TaskDescription string
|
||||||
|
LogFile string
|
||||||
|
XMLEscapedCommand string
|
||||||
|
}
|
||||||
|
|
||||||
|
var psEscape = strings.NewReplacer(
|
||||||
|
"$", "`$",
|
||||||
|
"\"", "`\"",
|
||||||
|
"`", "``",
|
||||||
|
"'", "`'",
|
||||||
|
)
|
||||||
|
|
||||||
|
var elevatedTemplate = template.Must(template.New("ElevatedCommand").Parse(`
|
||||||
|
$name = "{{.TaskName}}"
|
||||||
|
$log = [System.Environment]::ExpandEnvironmentVariables("{{.LogFile}}")
|
||||||
|
$s = New-Object -ComObject "Schedule.Service"
|
||||||
|
$s.Connect()
|
||||||
|
$t = $s.NewTask($null)
|
||||||
|
$xml = [xml]@'
|
||||||
|
<?xml version="1.0" encoding="UTF-16"?>
|
||||||
|
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
||||||
|
<RegistrationInfo>
|
||||||
|
<Description>{{.TaskDescription}}</Description>
|
||||||
|
</RegistrationInfo>
|
||||||
|
<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>false</StopOnIdleEnd>
|
||||||
|
<RestartOnIdle>false</RestartOnIdle>
|
||||||
|
</IdleSettings>
|
||||||
|
<AllowStartOnDemand>true</AllowStartOnDemand>
|
||||||
|
<Enabled>true</Enabled>
|
||||||
|
<Hidden>false</Hidden>
|
||||||
|
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
||||||
|
<WakeToRun>false</WakeToRun>
|
||||||
|
<ExecutionTimeLimit>PT24H</ExecutionTimeLimit>
|
||||||
|
<Priority>4</Priority>
|
||||||
|
</Settings>
|
||||||
|
<Actions Context="Author">
|
||||||
|
<Exec>
|
||||||
|
<Command>cmd</Command>
|
||||||
|
<Arguments>/c {{.XMLEscapedCommand}}</Arguments>
|
||||||
|
</Exec>
|
||||||
|
</Actions>
|
||||||
|
</Task>
|
||||||
|
'@
|
||||||
|
$logon_type = 1
|
||||||
|
$password = "{{.Password}}"
|
||||||
|
if ($password.Length -eq 0) {
|
||||||
|
$logon_type = 5
|
||||||
|
$password = $null
|
||||||
|
$ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
|
||||||
|
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI)
|
||||||
|
$node = $xml.SelectSingleNode("/ns:Task/ns:Principals/ns:Principal/ns:LogonType", $ns)
|
||||||
|
$node.ParentNode.RemoveChild($node) | Out-Null
|
||||||
|
}
|
||||||
|
$t.XmlText = $xml.OuterXml
|
||||||
|
if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}
|
||||||
|
$f = $s.GetFolder("\")
|
||||||
|
$f.RegisterTaskDefinition($name, $t, 6, "{{.User}}", $password, $logon_type, $null) | Out-Null
|
||||||
|
$t = $f.GetTask("\$name")
|
||||||
|
$t.Run($null) | Out-Null
|
||||||
|
$timeout = 10
|
||||||
|
$sec = 0
|
||||||
|
while ((!($t.state -eq 4)) -and ($sec -lt $timeout)) {
|
||||||
|
Start-Sleep -s 1
|
||||||
|
$sec++
|
||||||
|
}
|
||||||
|
|
||||||
|
$line = 0
|
||||||
|
do {
|
||||||
|
Start-Sleep -m 100
|
||||||
|
if (Test-Path $log) {
|
||||||
|
Get-Content $log | select -skip $line | ForEach {
|
||||||
|
$line += 1
|
||||||
|
Write-Output "$_"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (!($t.state -eq 3))
|
||||||
|
$result = $t.LastTaskResult
|
||||||
|
if (Test-Path $log) {
|
||||||
|
Remove-Item $log -Force -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
}
|
||||||
|
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($s) | Out-Null
|
||||||
|
exit $result`))
|
||||||
|
|
||||||
|
func GenerateElevatedRunner(command string, p ElevatedProvisioner) (uploadedPath string, err error) {
|
||||||
|
log.Printf("Building elevated command wrapper for: %s", command)
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
// Output from the elevated command cannot be returned directly to the
|
||||||
|
// Packer console. In order to be able to view output from elevated
|
||||||
|
// commands and scripts an indirect approach is used by which the commands
|
||||||
|
// output is first redirected to file. The output file is then 'watched'
|
||||||
|
// by Packer while the elevated command is running and any content
|
||||||
|
// appearing in the file is written out to the console. Below the portion
|
||||||
|
// of command required to redirect output from the command to file is
|
||||||
|
// built and appended to the existing command string
|
||||||
|
taskName := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||||
|
// Only use %ENVVAR% format for environment variables when setting the log
|
||||||
|
// file path; Do NOT use $env:ENVVAR format as it won't be expanded
|
||||||
|
// correctly in the elevatedTemplate
|
||||||
|
logFile := `%SYSTEMROOT%/Temp/` + taskName + ".out"
|
||||||
|
command += fmt.Sprintf(" > %s 2>&1", logFile)
|
||||||
|
|
||||||
|
// elevatedTemplate wraps the command in a single quoted XML text string
|
||||||
|
// so we need to escape characters considered 'special' in XML.
|
||||||
|
err = xml.EscapeText(&buffer, []byte(command))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error escaping characters special to XML in command %s: %s", command, err)
|
||||||
|
}
|
||||||
|
escapedCommand := buffer.String()
|
||||||
|
log.Printf("Command [%s] converted to [%s] for use in XML string", command, escapedCommand)
|
||||||
|
buffer.Reset()
|
||||||
|
|
||||||
|
// Escape chars special to PowerShell in the ElevatedUser string
|
||||||
|
elevatedUser := p.ElevatedUser()
|
||||||
|
escapedElevatedUser := psEscape.Replace(elevatedUser)
|
||||||
|
if escapedElevatedUser != elevatedUser {
|
||||||
|
log.Printf("Elevated user %s converted to %s after escaping chars special to PowerShell",
|
||||||
|
elevatedUser, escapedElevatedUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape chars special to PowerShell in the ElevatedPassword string
|
||||||
|
elevatedPassword := p.ElevatedPassword()
|
||||||
|
escapedElevatedPassword := psEscape.Replace(elevatedPassword)
|
||||||
|
if escapedElevatedPassword != elevatedPassword {
|
||||||
|
log.Printf("Elevated password %s converted to %s after escaping chars special to PowerShell",
|
||||||
|
elevatedPassword, escapedElevatedPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate command
|
||||||
|
err = elevatedTemplate.Execute(&buffer, elevatedOptions{
|
||||||
|
User: escapedElevatedUser,
|
||||||
|
Password: escapedElevatedPassword,
|
||||||
|
TaskName: taskName,
|
||||||
|
TaskDescription: "Packer elevated task",
|
||||||
|
LogFile: logFile,
|
||||||
|
XMLEscapedCommand: escapedCommand,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error creating elevated template: %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
uuid := uuid.TimeOrderedUUID()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("powershell -executionpolicy bypass -file \"%s\"", path), err
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package provisioner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testConfig() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"inline": []interface{}{"foo", "bar"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvisioner_GenerateElevatedRunner(t *testing.T) {
|
||||||
|
|
||||||
|
// Non-elevated
|
||||||
|
config := testConfig()
|
||||||
|
p := new(packer.MockProvisioner)
|
||||||
|
p.Prepare(config)
|
||||||
|
comm := new(packer.MockCommunicator)
|
||||||
|
p.ProvCommunicator = comm
|
||||||
|
path, err := GenerateElevatedRunner("whoami", p)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Did not expect error: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if comm.UploadCalled != true {
|
||||||
|
t.Fatalf("Should have uploaded file")
|
||||||
|
}
|
||||||
|
|
||||||
|
matched, _ := regexp.MatchString("C:/Windows/Temp/packer-elevated-shell.*", path)
|
||||||
|
if !matched {
|
||||||
|
t.Fatalf("Got unexpected file: %s", path)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,100 +0,0 @@
|
||||||
package powershell
|
|
||||||
|
|
||||||
import (
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
type elevatedOptions struct {
|
|
||||||
User string
|
|
||||||
Password string
|
|
||||||
TaskName string
|
|
||||||
TaskDescription string
|
|
||||||
LogFile string
|
|
||||||
XMLEscapedCommand string
|
|
||||||
}
|
|
||||||
|
|
||||||
var elevatedTemplate = template.Must(template.New("ElevatedCommand").Parse(`
|
|
||||||
$name = "{{.TaskName}}"
|
|
||||||
$log = [System.Environment]::ExpandEnvironmentVariables("{{.LogFile}}")
|
|
||||||
$s = New-Object -ComObject "Schedule.Service"
|
|
||||||
$s.Connect()
|
|
||||||
$t = $s.NewTask($null)
|
|
||||||
$xml = [xml]@'
|
|
||||||
<?xml version="1.0" encoding="UTF-16"?>
|
|
||||||
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
||||||
<RegistrationInfo>
|
|
||||||
<Description>{{.TaskDescription}}</Description>
|
|
||||||
</RegistrationInfo>
|
|
||||||
<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>false</StopOnIdleEnd>
|
|
||||||
<RestartOnIdle>false</RestartOnIdle>
|
|
||||||
</IdleSettings>
|
|
||||||
<AllowStartOnDemand>true</AllowStartOnDemand>
|
|
||||||
<Enabled>true</Enabled>
|
|
||||||
<Hidden>false</Hidden>
|
|
||||||
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
|
||||||
<WakeToRun>false</WakeToRun>
|
|
||||||
<ExecutionTimeLimit>PT24H</ExecutionTimeLimit>
|
|
||||||
<Priority>4</Priority>
|
|
||||||
</Settings>
|
|
||||||
<Actions Context="Author">
|
|
||||||
<Exec>
|
|
||||||
<Command>cmd</Command>
|
|
||||||
<Arguments>/c {{.XMLEscapedCommand}}</Arguments>
|
|
||||||
</Exec>
|
|
||||||
</Actions>
|
|
||||||
</Task>
|
|
||||||
'@
|
|
||||||
$logon_type = 1
|
|
||||||
$password = "{{.Password}}"
|
|
||||||
if ($password.Length -eq 0) {
|
|
||||||
$logon_type = 5
|
|
||||||
$password = $null
|
|
||||||
$ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
|
|
||||||
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI)
|
|
||||||
$node = $xml.SelectSingleNode("/ns:Task/ns:Principals/ns:Principal/ns:LogonType", $ns)
|
|
||||||
$node.ParentNode.RemoveChild($node) | Out-Null
|
|
||||||
}
|
|
||||||
$t.XmlText = $xml.OuterXml
|
|
||||||
if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}
|
|
||||||
$f = $s.GetFolder("\")
|
|
||||||
$f.RegisterTaskDefinition($name, $t, 6, "{{.User}}", $password, $logon_type, $null) | Out-Null
|
|
||||||
$t = $f.GetTask("\$name")
|
|
||||||
$t.Run($null) | Out-Null
|
|
||||||
$timeout = 10
|
|
||||||
$sec = 0
|
|
||||||
while ((!($t.state -eq 4)) -and ($sec -lt $timeout)) {
|
|
||||||
Start-Sleep -s 1
|
|
||||||
$sec++
|
|
||||||
}
|
|
||||||
|
|
||||||
$line = 0
|
|
||||||
do {
|
|
||||||
Start-Sleep -m 100
|
|
||||||
if (Test-Path $log) {
|
|
||||||
Get-Content $log | select -skip $line | ForEach {
|
|
||||||
$line += 1
|
|
||||||
Write-Output "$_"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (!($t.state -eq 3))
|
|
||||||
$result = $t.LastTaskResult
|
|
||||||
if (Test-Path $log) {
|
|
||||||
Remove-Item $log -Force -ErrorAction SilentlyContinue | Out-Null
|
|
||||||
}
|
|
||||||
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($s) | Out-Null
|
|
||||||
exit $result`))
|
|
|
@ -4,8 +4,6 @@ package powershell
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"encoding/xml"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -20,6 +18,7 @@ import (
|
||||||
commonhelper "github.com/hashicorp/packer/helper/common"
|
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||||
"github.com/hashicorp/packer/helper/config"
|
"github.com/hashicorp/packer/helper/config"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/hashicorp/packer/provisioner"
|
||||||
"github.com/hashicorp/packer/template/interpolate"
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -507,90 +506,29 @@ func (p *Provisioner) createCommandTextPrivileged() (command string, err error)
|
||||||
return "", fmt.Errorf("Error processing command: %s", err)
|
return "", fmt.Errorf("Error processing command: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OK so we need an elevated shell runner to wrap our command, this is
|
command, err = provisioner.GenerateElevatedRunner(command, p)
|
||||||
// going to have its own path generate the script and update the command
|
|
||||||
// runner in the process
|
|
||||||
path, err := p.generateElevatedRunner(command)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Error generating elevated runner: %s", err)
|
return "", fmt.Errorf("Error generating elevated runner: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the path to the elevated shell wrapper
|
|
||||||
command = fmt.Sprintf("powershell -executionpolicy bypass -file \"%s\"", path)
|
|
||||||
|
|
||||||
return command, err
|
return command, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath string, err error) {
|
func (p *Provisioner) Communicator() packer.Communicator {
|
||||||
log.Printf("Building elevated command wrapper for: %s", command)
|
return p.communicator
|
||||||
|
}
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
func (p *Provisioner) ElevatedUser() string {
|
||||||
|
return p.config.ElevatedUser
|
||||||
|
}
|
||||||
|
|
||||||
// Output from the elevated command cannot be returned directly to the
|
func (p *Provisioner) ElevatedPassword() string {
|
||||||
// Packer console. In order to be able to view output from elevated
|
|
||||||
// commands and scripts an indirect approach is used by which the commands
|
|
||||||
// output is first redirected to file. The output file is then 'watched'
|
|
||||||
// by Packer while the elevated command is running and any content
|
|
||||||
// appearing in the file is written out to the console. Below the portion
|
|
||||||
// of command required to redirect output from the command to file is
|
|
||||||
// built and appended to the existing command string
|
|
||||||
taskName := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
|
||||||
// Only use %ENVVAR% format for environment variables when setting the log
|
|
||||||
// file path; Do NOT use $env:ENVVAR format as it won't be expanded
|
|
||||||
// correctly in the elevatedTemplate
|
|
||||||
logFile := `%SYSTEMROOT%/Temp/` + taskName + ".out"
|
|
||||||
command += fmt.Sprintf(" > %s 2>&1", logFile)
|
|
||||||
|
|
||||||
// elevatedTemplate wraps the command in a single quoted XML text string
|
|
||||||
// so we need to escape characters considered 'special' in XML.
|
|
||||||
err = xml.EscapeText(&buffer, []byte(command))
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Error escaping characters special to XML in command %s: %s", command, err)
|
|
||||||
}
|
|
||||||
escapedCommand := buffer.String()
|
|
||||||
log.Printf("Command [%s] converted to [%s] for use in XML string", command, escapedCommand)
|
|
||||||
buffer.Reset()
|
|
||||||
|
|
||||||
// Escape chars special to PowerShell in the ElevatedUser string
|
|
||||||
escapedElevatedUser := psEscape.Replace(p.config.ElevatedUser)
|
|
||||||
if escapedElevatedUser != p.config.ElevatedUser {
|
|
||||||
log.Printf("Elevated user %s converted to %s after escaping chars special to PowerShell",
|
|
||||||
p.config.ElevatedUser, escapedElevatedUser)
|
|
||||||
}
|
|
||||||
// Replace ElevatedPassword for winrm users who used this feature
|
// Replace ElevatedPassword for winrm users who used this feature
|
||||||
p.config.ctx.Data = &EnvVarsTemplate{
|
p.config.ctx.Data = &EnvVarsTemplate{
|
||||||
WinRMPassword: getWinRMPassword(p.config.PackerBuildName),
|
WinRMPassword: getWinRMPassword(p.config.PackerBuildName),
|
||||||
}
|
}
|
||||||
|
|
||||||
p.config.ElevatedPassword, _ = interpolate.Render(p.config.ElevatedPassword, &p.config.ctx)
|
elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx)
|
||||||
|
|
||||||
// Escape chars special to PowerShell in the ElevatedPassword string
|
return elevatedPassword
|
||||||
escapedElevatedPassword := psEscape.Replace(p.config.ElevatedPassword)
|
|
||||||
if escapedElevatedPassword != p.config.ElevatedPassword {
|
|
||||||
log.Printf("Elevated password %s converted to %s after escaping chars special to PowerShell",
|
|
||||||
p.config.ElevatedPassword, escapedElevatedPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate command
|
|
||||||
err = elevatedTemplate.Execute(&buffer, elevatedOptions{
|
|
||||||
User: escapedElevatedUser,
|
|
||||||
Password: escapedElevatedPassword,
|
|
||||||
TaskName: taskName,
|
|
||||||
TaskDescription: "Packer elevated task",
|
|
||||||
LogFile: logFile,
|
|
||||||
XMLEscapedCommand: escapedCommand,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating elevated template: %s", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
uuid := uuid.TimeOrderedUUID()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return path, err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -654,30 +654,6 @@ func TestProvision_uploadEnvVars(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProvision_generateElevatedShellRunner(t *testing.T) {
|
|
||||||
|
|
||||||
// Non-elevated
|
|
||||||
config := testConfig()
|
|
||||||
p := new(Provisioner)
|
|
||||||
p.Prepare(config)
|
|
||||||
comm := new(packer.MockCommunicator)
|
|
||||||
p.communicator = comm
|
|
||||||
path, err := p.generateElevatedRunner("whoami")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Did not expect error: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if comm.UploadCalled != true {
|
|
||||||
t.Fatalf("Should have uploaded file")
|
|
||||||
}
|
|
||||||
|
|
||||||
matched, _ := regexp.MatchString("C:/Windows/Temp/packer-elevated-shell.*", path)
|
|
||||||
if !matched {
|
|
||||||
t.Fatalf("Got unexpected file: %s", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRetryable(t *testing.T) {
|
func TestRetryable(t *testing.T) {
|
||||||
config := testConfig()
|
config := testConfig()
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/common"
|
"github.com/hashicorp/packer/common"
|
||||||
|
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||||
"github.com/hashicorp/packer/helper/config"
|
"github.com/hashicorp/packer/helper/config"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/hashicorp/packer/provisioner"
|
"github.com/hashicorp/packer/provisioner"
|
||||||
|
@ -65,6 +66,12 @@ type Config struct {
|
||||||
// The directory from which the command will be executed.
|
// The directory from which the command will be executed.
|
||||||
// Packer requires the directory to exist when running puppet.
|
// Packer requires the directory to exist when running puppet.
|
||||||
WorkingDir string `mapstructure:"working_directory"`
|
WorkingDir string `mapstructure:"working_directory"`
|
||||||
|
|
||||||
|
// Instructs the communicator to run the remote script as a Windows
|
||||||
|
// scheduled task, effectively elevating the remote user by impersonating
|
||||||
|
// a logged-in user
|
||||||
|
ElevatedUser string `mapstructure:"elevated_user"`
|
||||||
|
ElevatedPassword string `mapstructure:"elevated_password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type guestOSTypeConfig struct {
|
type guestOSTypeConfig struct {
|
||||||
|
@ -117,6 +124,7 @@ var guestOSTypeConfigs = map[string]guestOSTypeConfig{
|
||||||
|
|
||||||
type Provisioner struct {
|
type Provisioner struct {
|
||||||
config Config
|
config Config
|
||||||
|
communicator packer.Communicator
|
||||||
guestOSTypeConfig guestOSTypeConfig
|
guestOSTypeConfig guestOSTypeConfig
|
||||||
guestCommands *provisioner.GuestCommands
|
guestCommands *provisioner.GuestCommands
|
||||||
}
|
}
|
||||||
|
@ -135,7 +143,17 @@ type ExecuteTemplate struct {
|
||||||
WorkingDir string
|
WorkingDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EnvVarsTemplate struct {
|
||||||
|
WinRMPassword string
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||||
|
// Create passthrough for winrm password so we can fill it in once we know
|
||||||
|
// it
|
||||||
|
p.config.ctx.Data = &EnvVarsTemplate{
|
||||||
|
WinRMPassword: `{{.WinRMPassword}}`,
|
||||||
|
}
|
||||||
|
|
||||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &p.config.ctx,
|
InterpolateContext: &p.config.ctx,
|
||||||
|
@ -240,6 +258,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||||
|
|
||||||
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||||
ui.Say("Provisioning with Puppet...")
|
ui.Say("Provisioning with Puppet...")
|
||||||
|
p.communicator = comm
|
||||||
ui.Message("Creating Puppet staging directory...")
|
ui.Message("Creating Puppet staging directory...")
|
||||||
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
|
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
|
||||||
return fmt.Errorf("Error creating staging directory: %s", err)
|
return fmt.Errorf("Error creating staging directory: %s", err)
|
||||||
|
@ -316,6 +335,13 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.config.ElevatedUser != "" {
|
||||||
|
command, err = provisioner.GenerateElevatedRunner(command, p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cmd := &packer.RemoteCmd{
|
cmd := &packer.RemoteCmd{
|
||||||
Command: command,
|
Command: command,
|
||||||
}
|
}
|
||||||
|
@ -432,10 +458,7 @@ func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir stri
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
|
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
|
||||||
cmd := &packer.RemoteCmd{
|
cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
|
||||||
Command: fmt.Sprintf("rm -fr '%s'", dir),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -460,3 +483,28 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
|
||||||
|
|
||||||
return comm.UploadDir(dst, src, nil)
|
return comm.UploadDir(dst, src, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getWinRMPassword(buildName string) string {
|
||||||
|
winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName)
|
||||||
|
packer.LogSecretFilter.Set(winRMPass)
|
||||||
|
return winRMPass
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provisioner) Communicator() packer.Communicator {
|
||||||
|
return p.communicator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provisioner) ElevatedUser() string {
|
||||||
|
return p.config.ElevatedUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provisioner) ElevatedPassword() string {
|
||||||
|
// Replace ElevatedPassword for winrm users who used this feature
|
||||||
|
p.config.ctx.Data = &EnvVarsTemplate{
|
||||||
|
WinRMPassword: getWinRMPassword(p.config.PackerBuildName),
|
||||||
|
}
|
||||||
|
|
||||||
|
elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx)
|
||||||
|
|
||||||
|
return elevatedPassword
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/common"
|
"github.com/hashicorp/packer/common"
|
||||||
|
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||||
"github.com/hashicorp/packer/helper/config"
|
"github.com/hashicorp/packer/helper/config"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/hashicorp/packer/provisioner"
|
"github.com/hashicorp/packer/provisioner"
|
||||||
|
@ -63,6 +64,12 @@ type Config struct {
|
||||||
// The directory from which the command will be executed.
|
// The directory from which the command will be executed.
|
||||||
// Packer requires the directory to exist when running puppet.
|
// Packer requires the directory to exist when running puppet.
|
||||||
WorkingDir string `mapstructure:"working_directory"`
|
WorkingDir string `mapstructure:"working_directory"`
|
||||||
|
|
||||||
|
// Instructs the communicator to run the remote script as a Windows
|
||||||
|
// scheduled task, effectively elevating the remote user by impersonating
|
||||||
|
// a logged-in user
|
||||||
|
ElevatedUser string `mapstructure:"elevated_user"`
|
||||||
|
ElevatedPassword string `mapstructure:"elevated_password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type guestOSTypeConfig struct {
|
type guestOSTypeConfig struct {
|
||||||
|
@ -112,6 +119,7 @@ var guestOSTypeConfigs = map[string]guestOSTypeConfig{
|
||||||
|
|
||||||
type Provisioner struct {
|
type Provisioner struct {
|
||||||
config Config
|
config Config
|
||||||
|
communicator packer.Communicator
|
||||||
guestOSTypeConfig guestOSTypeConfig
|
guestOSTypeConfig guestOSTypeConfig
|
||||||
guestCommands *provisioner.GuestCommands
|
guestCommands *provisioner.GuestCommands
|
||||||
}
|
}
|
||||||
|
@ -129,7 +137,17 @@ type ExecuteTemplate struct {
|
||||||
WorkingDir string
|
WorkingDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EnvVarsTemplate struct {
|
||||||
|
WinRMPassword string
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||||
|
// Create passthrough for winrm password so we can fill it in once we know
|
||||||
|
// it
|
||||||
|
p.config.ctx.Data = &EnvVarsTemplate{
|
||||||
|
WinRMPassword: `{{.WinRMPassword}}`,
|
||||||
|
}
|
||||||
|
|
||||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &p.config.ctx,
|
InterpolateContext: &p.config.ctx,
|
||||||
|
@ -210,6 +228,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||||
|
|
||||||
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||||
ui.Say("Provisioning with Puppet...")
|
ui.Say("Provisioning with Puppet...")
|
||||||
|
p.communicator = comm
|
||||||
ui.Message("Creating Puppet staging directory...")
|
ui.Message("Creating Puppet staging directory...")
|
||||||
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
|
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
|
||||||
return fmt.Errorf("Error creating staging directory: %s", err)
|
return fmt.Errorf("Error creating staging directory: %s", err)
|
||||||
|
@ -269,6 +288,13 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.config.ElevatedUser != "" {
|
||||||
|
command, err = provisioner.GenerateElevatedRunner(command, p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cmd := &packer.RemoteCmd{
|
cmd := &packer.RemoteCmd{
|
||||||
Command: command,
|
Command: command,
|
||||||
}
|
}
|
||||||
|
@ -321,10 +347,7 @@ func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir stri
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
|
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
|
||||||
cmd := &packer.RemoteCmd{
|
cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
|
||||||
Command: fmt.Sprintf("rm -fr '%s'", dir),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -349,3 +372,28 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
|
||||||
|
|
||||||
return comm.UploadDir(dst, src, nil)
|
return comm.UploadDir(dst, src, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getWinRMPassword(buildName string) string {
|
||||||
|
winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName)
|
||||||
|
packer.LogSecretFilter.Set(winRMPass)
|
||||||
|
return winRMPass
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provisioner) Communicator() packer.Communicator {
|
||||||
|
return p.communicator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provisioner) ElevatedUser() string {
|
||||||
|
return p.config.ElevatedUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provisioner) ElevatedPassword() string {
|
||||||
|
// Replace ElevatedPassword for winrm users who used this feature
|
||||||
|
p.config.ctx.Data = &EnvVarsTemplate{
|
||||||
|
WinRMPassword: getWinRMPassword(p.config.PackerBuildName),
|
||||||
|
}
|
||||||
|
|
||||||
|
elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx)
|
||||||
|
|
||||||
|
return elevatedPassword
|
||||||
|
}
|
||||||
|
|
|
@ -51,6 +51,11 @@ configuration is actually required.
|
||||||
should use a custom configuration template. See the dedicated "Chef
|
should use a custom configuration template. See the dedicated "Chef
|
||||||
Configuration" section below for more details.
|
Configuration" section below for more details.
|
||||||
|
|
||||||
|
- `elevated_user` and `elevated_password` (string) - If specified, Chef will
|
||||||
|
be run with elevated privileges using the given Windows user. See the
|
||||||
|
[powershell](/docs/provisionders/powershell.html) provisioner for the full
|
||||||
|
details.
|
||||||
|
|
||||||
- `encrypted_data_bag_secret_path` (string) - The path to the file containing
|
- `encrypted_data_bag_secret_path` (string) - The path to the file containing
|
||||||
the secret for encrypted data bags. By default, this is empty, so no secret
|
the secret for encrypted data bags. By default, this is empty, so no secret
|
||||||
will be available.
|
will be available.
|
||||||
|
|
|
@ -115,6 +115,11 @@ manifests you should use `manifest_file` instead.
|
||||||
be run. If using Hiera files with relative paths, this option can be
|
be run. If using Hiera files with relative paths, this option can be
|
||||||
helpful. (default: `staging_directory`)
|
helpful. (default: `staging_directory`)
|
||||||
|
|
||||||
|
- `elevated_user` and `elevated_password` (string) - If specified, Puppet
|
||||||
|
will be run with elevated privileges using the given Windows user. See the
|
||||||
|
[powershell](/docs/provisioners/powershell.html) provisioner for the full
|
||||||
|
details.
|
||||||
|
|
||||||
## Execute Command
|
## Execute Command
|
||||||
|
|
||||||
By default, Packer uses the following command (broken across multiple lines for
|
By default, Packer uses the following command (broken across multiple lines for
|
||||||
|
|
|
@ -101,6 +101,11 @@ listed below:
|
||||||
be run. If using Hiera files with relative paths, this option can be
|
be run. If using Hiera files with relative paths, this option can be
|
||||||
helpful. (default: `staging_directory`)
|
helpful. (default: `staging_directory`)
|
||||||
|
|
||||||
|
- `elevated_user` and `elevated_password` (string) - If specified, Puppet
|
||||||
|
will be run with elevated privileges using the given Windows user. See the
|
||||||
|
[powershell](/docs/provisioners/powershell.html) provisioner for the full
|
||||||
|
details.
|
||||||
|
|
||||||
## Execute Command
|
## Execute Command
|
||||||
|
|
||||||
By default, Packer uses the following command (broken across multiple lines for
|
By default, Packer uses the following command (broken across multiple lines for
|
||||||
|
|
Loading…
Reference in New Issue