diff --git a/builder/amazon/common/step_get_password.go b/builder/amazon/common/step_get_password.go index 5e2a236c2..c9011d930 100644 --- a/builder/amazon/common/step_get_password.go +++ b/builder/amazon/common/step_get_password.go @@ -12,6 +12,7 @@ import ( "time" "github.com/aws/aws-sdk-go/service/ec2" + commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -92,11 +93,15 @@ WaitLoop: ui.Message(fmt.Sprintf( "Password (since debug is enabled): %s", s.Comm.WinRMPassword)) } + // store so that we can access this later during provisioning + commonhelper.SetSharedState("winrm_password", s.Comm.WinRMPassword) return multistep.ActionContinue } -func (s *StepGetPassword) Cleanup(multistep.StateBag) {} +func (s *StepGetPassword) Cleanup(multistep.StateBag) { + commonhelper.RemoveSharedStateFile("winrm_password") +} func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) { ec2conn := state.Get("ec2").(*ec2.EC2) diff --git a/common/step_http_server.go b/common/step_http_server.go index e90c6ef60..953ef38c1 100644 --- a/common/step_http_server.go +++ b/common/step_http_server.go @@ -3,14 +3,12 @@ package common import ( "context" "fmt" - "io/ioutil" "log" "math/rand" "net" "net/http" - "os" - "path/filepath" + "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) @@ -77,25 +75,21 @@ func (s *StepHTTPServer) Run(_ context.Context, state multistep.StateBag) multis return multistep.ActionContinue } -func httpAddrFilename(suffix string) string { - uuid := os.Getenv("PACKER_RUN_UUID") - return filepath.Join(os.TempDir(), fmt.Sprintf("packer-%s-%s", uuid, suffix)) -} - func SetHTTPPort(port string) error { - return ioutil.WriteFile(httpAddrFilename("port"), []byte(port), 0644) + return common.SetSharedState("port", port) } func SetHTTPIP(ip string) error { - return ioutil.WriteFile(httpAddrFilename("ip"), []byte(ip), 0644) + return common.SetSharedState("ip", ip) } func GetHTTPAddr() string { - ip, err := ioutil.ReadFile(httpAddrFilename("ip")) + ip, err := common.RetrieveSharedState("ip") if err != nil { return "" } - port, err := ioutil.ReadFile(httpAddrFilename("port")) + + port, err := common.RetrieveSharedState("port") if err != nil { return "" } @@ -107,6 +101,6 @@ func (s *StepHTTPServer) Cleanup(multistep.StateBag) { // Close the listener so that the HTTP server stops s.l.Close() } - os.Remove(httpAddrFilename("port")) - os.Remove(httpAddrFilename("ip")) + common.RemoveSharedStateFile("port") + common.RemoveSharedStateFile("ip") } diff --git a/helper/common/shared_state.go b/helper/common/shared_state.go new file mode 100644 index 000000000..92ffbaeaf --- /dev/null +++ b/helper/common/shared_state.go @@ -0,0 +1,31 @@ +package common + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +// Used to set variables which we need to access later in the build, where +// state bag and config information won't work +func sharedStateFilename(suffix string) string { + uuid := os.Getenv("PACKER_RUN_UUID") + return filepath.Join(os.TempDir(), fmt.Sprintf("packer-%s-%s", uuid, suffix)) +} + +func SetSharedState(key string, value string) error { + return ioutil.WriteFile(sharedStateFilename(key), []byte(value), 0600) +} + +func RetrieveSharedState(key string) (string, error) { + value, err := ioutil.ReadFile(sharedStateFilename(key)) + if err != nil { + return "", err + } + return string(value), nil +} + +func RemoveSharedStateFile(key string) { + os.Remove(sharedStateFilename(key)) +} diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 9655f6fed..6b7adfac6 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/uuid" + commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -99,11 +100,22 @@ type Provisioner struct { } type ExecuteCommandTemplate struct { - Vars string - Path string + Vars string + Path string + WinRMPassword string +} + +type EnvVarsTemplate struct { + WinRMPassword string } func (p *Provisioner) Prepare(raws ...interface{}) error { + //Create passthrough for winrm password so we can fill it in once we know it + log.Printf("MEGAN context is %#v", p.config.ctx) + p.config.ctx.Data = &EnvVarsTemplate{ + WinRMPassword: `{{.WinRMPassword}}`, + } + err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &p.config.ctx, @@ -236,6 +248,7 @@ func extractScript(p *Provisioner) (string, error) { } func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { + log.Printf("MEGAN context is %#v", p.config.ctx) ui.Say(fmt.Sprintf("Provisioning with Powershell...")) p.communicator = comm @@ -358,13 +371,22 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) { // Always available Packer provided env vars envVars["PACKER_BUILD_NAME"] = p.config.PackerBuildName envVars["PACKER_BUILDER_TYPE"] = p.config.PackerBuilderType + httpAddr := common.GetHTTPAddr() if httpAddr != "" { envVars["PACKER_HTTP_ADDR"] = httpAddr } + // interpolate environment variables + p.config.ctx.Data = &EnvVarsTemplate{ + WinRMPassword: getWinRMPassword(), + } // Split vars into key/value components for _, envVar := range p.config.Vars { + envVar, err := interpolate.Render(envVar, &p.config.ctx) + if err != nil { + return + } keyValue := strings.SplitN(envVar, "=", 2) // Escape chars special to PS in each env var value escapedEnvVarValue := psEscape.Replace(keyValue[1]) @@ -423,8 +445,9 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro } p.config.ctx.Data = &ExecuteCommandTemplate{ - Path: p.config.RemotePath, - Vars: envVarPath, + Path: p.config.RemotePath, + Vars: envVarPath, + WinRMPassword: getWinRMPassword(), } command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) @@ -436,6 +459,11 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro return command, nil } +func getWinRMPassword() string { + winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password") + return winRMPass + +} 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) @@ -444,8 +472,9 @@ func (p *Provisioner) createCommandTextPrivileged() (command string, err error) } p.config.ctx.Data = &ExecuteCommandTemplate{ - Path: p.config.RemotePath, - Vars: envVarPath, + Path: p.config.RemotePath, + Vars: envVarPath, + WinRMPassword: getWinRMPassword(), } command, err = interpolate.Render(p.config.ElevatedExecuteCommand, &p.config.ctx) if err != nil { @@ -501,6 +530,12 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin 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 + p.config.ctx.Data = &EnvVarsTemplate{ + WinRMPassword: getWinRMPassword(), + } + + p.config.ElevatedPassword, _ = interpolate.Render(p.config.ElevatedPassword, &p.config.ctx) // Escape chars special to PowerShell in the ElevatedPassword string escapedElevatedPassword := psEscape.Replace(p.config.ElevatedPassword) diff --git a/website/source/docs/provisioners/powershell.html.md b/website/source/docs/provisioners/powershell.html.md index c51dfc59b..2051efff3 100644 --- a/website/source/docs/provisioners/powershell.html.md +++ b/website/source/docs/provisioners/powershell.html.md @@ -72,7 +72,20 @@ Optional parameters: - `environment_vars` (array of strings) - An array of key/value pairs to inject prior to the execute\_command. The format should be `key=value`. Packer injects some environmental variables by default into the - environment, as well, which are covered in the section below. + environment, as well, which are covered in the section below. If you are + using AWS and would like to use the randomly-generated unique + If you are running on AWS and would like to access the AWS-generated + Administrator password that Packer uses to connect to the instance via + WinRM, you can use the template variable `{{.WinRMPassword}}` to set this + as an environment variable. For example: + + ```json + { + "type": "powershell", + "environment_vars": "WINRMPASS={{.WinRMPassword}}", + "inline": ["Write-Host \"Automatically generated aws password is: $Env:WINRMPASS\""] + }, + ``` - `execute_command` (string) - The command to use to execute the script. By default this is as follows: @@ -89,7 +102,15 @@ Optional parameters: - `elevated_user` and `elevated_password` (string) - If specified, the PowerShell script will be run with elevated privileges using the given - Windows user. + Windows user. If you are running a build on AWS and would like to run using + the AWS-generated password that Packer uses to connect to the instance via, + WinRM, you may do so by using the template variable {{.WinRMPassword}}. + For example: + + ``` json + "elevated_user": "Administrator", + "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