Merge remote-tracking branch 'origin/master' into dynamic-source-ami

This commit is contained in:
Chris Lundquist 2016-09-02 01:43:03 +00:00
commit defdd1ecf3
25 changed files with 335 additions and 85 deletions

View File

@ -12,7 +12,7 @@ FOR BUGS:
Describe the problem and include the following information:
- Packer Version
- Packer version from `packer version`
- Host platform
- Debug log output from `PACKER_LOG=1 packer build template.json`.
Please paste this in a gist https://gist.github.com

View File

@ -26,6 +26,7 @@ IMPROVEMENTS:
* builder/digitalocean: Use `state_timeout` for unlock and off transitions.
[GH-3444]
* builder/google: Added support for `image_family` [GH-3503]
* builder/google: Use gcloud application default credentials. [GH-3655]
* builder/null: Can now be used with WinRM [GH-2525]
* builder/parallels: Now pauses between `boot_command` entries when running
with `-debug` [GH-3547]
@ -43,6 +44,8 @@ IMPROVEMENTS:
[GH-3347]
* builder/qemu: Now pauses between `boot_command` entries when running with
`-debug` [GH-3547]
* provisioner/ansible: Improved logging and error handling [GH-3477]
* provisioner/ansible-local: Support for ansible-galaxy [GH-3350] [GH-3836]
* provisioner/chef: Added `knife_command` option and added a correct default
value for Windows [GH-3622]
* provisioner/puppet: Added `execute_command` option [GH-3614]
@ -56,8 +59,11 @@ BUG FIXES:
* post-processor/vsphere: Fix upload failures with vsphere [GH-3321]
* provisioner/ansible: Properly set host key checking even when a custom ENV
is specified [GH-3568]
* builder/amazon: Use `temporary_key_pair_name` when specified. [GH-3739]
* builder/amazon: Add 0.5 cents to discovered spot price. [GH-3662]
* builder/azure: check for empty resource group [GH-3606]
* builder/azure: fix token validity test [GH-3609]
* builder/virtualbox: Respect `ssh_host` [GH-3617]
* builder/vmware: Re-introduce case sensitive VMX keys [GH-2707]
* builder/vmware: Don't check for poweron errors on ESXi [GH-3195]
* builder/vmware: Respect `ssh_host`/`winrm_host` on ESXi [GH-3738]

View File

@ -55,10 +55,14 @@ type RunConfig struct {
}
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
// if we are not given an explicit keypairname, create a temporary one
// If we are not given an explicit ssh_keypair_name,
// then create a temporary one, but only if the
// temporary_key_pair_name has not been provided.
if c.SSHKeyPairName == "" {
c.TemporaryKeyPairName = fmt.Sprintf(
"packer %s", uuid.TimeOrderedUUID())
if c.TemporaryKeyPairName == "" {
c.TemporaryKeyPairName = fmt.Sprintf(
"packer_%s", uuid.TimeOrderedUUID())
}
}
if c.WindowsPasswordTimeout == 0 {

View File

@ -3,6 +3,7 @@ package common
import (
"io/ioutil"
"os"
"regexp"
"testing"
"github.com/mitchellh/packer/helper/communicator"
@ -166,6 +167,21 @@ func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
}
if c.TemporaryKeyPairName == "" {
t.Fatal("keypair empty")
t.Fatal("keypair name is empty")
}
// Match prefix and UUID, e.g. "packer_5790d491-a0b8-c84c-c9d2-2aea55086550".
r := regexp.MustCompile(`\Apacker_(?:(?i)[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}?)\z`)
if !r.MatchString(c.TemporaryKeyPairName) {
t.Fatal("keypair name is not valid")
}
c.TemporaryKeyPairName = "ssh-key-123"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.TemporaryKeyPairName != "ssh-key-123" {
t.Fatal("keypair name does not match")
}
}

View File

@ -141,6 +141,10 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} else {
// Add 0.5 cents to minimum spot bid to ensure capacity will be available
// Avoids price-too-low error in active markets which can fluctuate
price = price + 0.005
}
spotPrice = strconv.FormatFloat(price, 'f', -1, 64)

View File

@ -118,21 +118,21 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
BlockDevices: b.config.BlockDevices,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
},
&stepTagEBSVolumes{

View File

@ -50,15 +50,20 @@ func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) {
// your service account.
client = conf.Client(oauth2.NoContext)
} else {
log.Printf("[INFO] Requesting Google token via GCE Service Role...")
client = &http.Client{
Transport: &oauth2.Transport{
// Fetch from Google Compute Engine's metadata server to retrieve
// an access token for the provided account.
// If no account is specified, "default" is used.
Source: google.ComputeTokenSource(""),
},
}
log.Printf("[INFO] Requesting Google token via GCE API Default Client Token Source...")
client, err = google.DefaultClient(oauth2.NoContext, DriverScopes...)
// The DefaultClient uses the DefaultTokenSource of the google lib.
// The DefaultTokenSource uses the "Application Default Credentials"
// It looks for credentials in the following places, preferring the first location found:
// 1. A JSON file whose path is specified by the
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
// 2. A JSON file in a location known to the gcloud command-line tool.
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
// 3. On Google App Engine it uses the appengine.AccessToken function.
// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches
// credentials from the metadata server.
// (In this final case any provided scopes are ignored.)
}
if err != nil {

View File

@ -7,8 +7,10 @@ import (
gossh "golang.org/x/crypto/ssh"
)
func CommHost(state multistep.StateBag) (string, error) {
return "127.0.0.1", nil
func CommHost(host string) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
return host, nil
}
}
func SSHPort(state multistep.StateBag) (int, error) {

View File

@ -21,6 +21,10 @@ type SSHConfig struct {
}
func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error {
if c.Comm.SSHHost == "" {
c.Comm.SSHHost = "127.0.0.1"
}
if c.SSHHostPortMin == 0 {
c.SSHHostPortMin = 2222
}

View File

@ -235,7 +235,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
Host: vboxcommon.CommHost,
Host: vboxcommon.CommHost(b.config.SSHConfig.Comm.SSHHost),
SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig),
SSHPort: vboxcommon.SSHPort,
},

View File

@ -104,7 +104,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
Host: vboxcommon.CommHost,
Host: vboxcommon.CommHost(b.config.SSHConfig.Comm.SSHHost),
SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig),
SSHPort: vboxcommon.SSHPort,
},

View File

@ -242,6 +242,11 @@ func (ESX5Driver) UpdateVMX(_, password string, port uint, data map[string]strin
func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) {
config := state.Get("config").(*Config)
sshc := config.SSHConfig.Comm
port := sshc.SSHPort
if sshc.Type == "winrm" {
port = sshc.WinRMPort
}
if address, ok := state.GetOk("vm_address"); ok {
return address.(string), nil
@ -286,7 +291,7 @@ func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) {
}
// When multiple NICs are connected to the same network, choose
// one that has a route back. This Dial should ensure that.
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", record["IPAddress"], d.Port), 2*time.Second)
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", record["IPAddress"], port), 2*time.Second)
if err != nil {
if e, ok := err.(*net.OpError); ok {
if e.Timeout() {

View File

@ -9,6 +9,8 @@ azure_group_name=
azure_storage_name=
azure_subscription_id= # Derived from the account after login
azure_tenant_id= # Derived from the account after login
location=
azure_object_id=
showhelp() {
echo "azure-setup"
@ -88,7 +90,7 @@ askSubscription() {
askName() {
echo ""
echo "Choose a name for your resource group, storage account, and client"
echo "Choose a name for your resource group, storage account and client"
echo "client. This is arbitrary, but it must not already be in use by"
echo "any of those resources. ALPHANUMERIC ONLY. Ex: mypackerbuild"
echo -n "> "
@ -113,9 +115,17 @@ askSecret() {
fi
}
askLocation() {
azure location list
echo ""
echo "Choose which region your resource group and storage account will be created."
echo -n "> "
read location
}
createResourceGroup() {
echo "==> Creating resource group"
azure group create -n $meta_name -l westus
azure group create -n $meta_name -l $location
if [ $? -eq 0 ]; then
azure_group_name=$meta_name
else
@ -126,7 +136,7 @@ createResourceGroup() {
createStorageAccount() {
echo "==> Creating storage account"
azure storage account create -g $meta_name -l westus --sku-name LRS --kind Storage $meta_name
azure storage account create -g $meta_name -l $location --sku-name LRS --kind Storage $meta_name
if [ $? -eq 0 ]; then
azure_storage_name=$meta_name
else
@ -135,18 +145,10 @@ createStorageAccount() {
fi
}
createApplication() {
echo "==> Creating application"
azure_client_id=$(azure ad app create -n $meta_name -i http://$meta_name --home-page http://$meta_name -p $azure_client_secret --json | jq -r .appId)
if [ $? -ne 0 ]; then
echo "Error creating application: $meta_name @ http://$meta_name"
exit 1
fi
}
createServicePrinciple() {
echo "==> Creating service principal"
azure ad sp create $azure_client_id
azure_object_id=$(azure ad sp create -n $meta_name --home-page http://$meta_name --identifier-uris http://$meta_name/example -p $azure_client_secret --json | jq -r .objectId)
azure_client_id=$(azure ad app show -c $meta_name --json | jq -r .[0].appId)
if [ $? -ne 0 ]; then
echo "Error creating service principal: $azure_client_id"
exit 1
@ -155,7 +157,7 @@ createServicePrinciple() {
createPermissions() {
echo "==> Creating permissions"
azure role assignment create -o "Owner" --spn http://$meta_name -c /subscriptions/$azure_subscription_id
azure role assignment create --objectId $azure_object_id -o "Owner" -c /subscriptions/$azure_subscription_id
# We want to use this more conservative scope but it does not work with the
# current implementation which uses temporary resource groups
# azure role assignment create --spn http://$meta_name -g $azure_group_name -o "API Management Service Contributor"
@ -169,11 +171,15 @@ showConfigs() {
echo ""
echo "Use the following configuration for your packer template:"
echo ""
echo "{"
echo " \"client_id\": \"$azure_client_id\","
echo " \"client_secret\": \"$azure_client_secret\","
echo " \"object_id\": \"$azure_object_id\","
echo " \"subscription_id\": \"$azure_subscription_id\","
echo " \"tenant_id\": \"$azure_tenant_id\","
echo " \"resource_group_name\": \"$azure_group_name\","
echo " \"storage_account\": \"$azure_storage_name\","
echo " \"subscription_id\": \"$azure_subscription_id\","
echo "}"
echo ""
}
@ -186,6 +192,7 @@ setup() {
askSubscription
askName
askSecret
askLocation
# Some of the resources take a while to converge in the API. To make the
# script more reliable we'll add a sleep after we create each resource.
@ -194,8 +201,6 @@ setup() {
sleep 5
createStorageAccount
sleep 5
createApplication
sleep 5
createServicePrinciple
sleep 5
createPermissions

View File

@ -52,6 +52,12 @@ type Config struct {
// The optional inventory groups
InventoryGroups []string `mapstructure:"inventory_groups"`
// The optional ansible-galaxy requirements file
GalaxyFile string `mapstructure:"galaxy_file"`
// The command to run ansible-galaxy
GalaxyCommand string
}
type Provisioner struct {
@ -74,6 +80,9 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
if p.config.Command == "" {
p.config.Command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 ansible-playbook"
}
if p.config.GalaxyCommand == "" {
p.config.GalaxyCommand = "ansible-galaxy"
}
if p.config.StagingDir == "" {
p.config.StagingDir = DefaultStagingDir
@ -94,6 +103,14 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
}
// Check that the galaxy file exists, if configured
if len(p.config.GalaxyFile) > 0 {
err = validateFileConfig(p.config.GalaxyFile, "galaxy_file", true)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
}
// Check that the playbook_dir directory exists, if configured
if len(p.config.PlaybookDir) > 0 {
if err := validateDirConfig(p.config.PlaybookDir, "playbook_dir"); err != nil {
@ -181,6 +198,15 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
}()
}
if len(p.config.GalaxyFile) > 0 {
ui.Message("Uploading galaxy file...")
src = p.config.GalaxyFile
dst = filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(src)))
if err := p.uploadFile(ui, comm, dst, src); err != nil {
return fmt.Errorf("Error uploading galaxy file: %s", err)
}
}
ui.Message("Uploading inventory file...")
src = p.config.InventoryFile
dst = filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(src)))
@ -242,6 +268,27 @@ func (p *Provisioner) Cancel() {
os.Exit(0)
}
func (p *Provisioner) executeGalaxy(ui packer.Ui, comm packer.Communicator) error {
rolesDir := filepath.ToSlash(filepath.Join(p.config.StagingDir, "roles"))
galaxyFile := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.GalaxyFile)))
// ansible-galaxy install -r requirements.yml -p roles/
command := fmt.Sprintf("cd %s && %s install -r %s -p %s",
p.config.StagingDir, p.config.GalaxyCommand, galaxyFile, rolesDir)
ui.Message(fmt.Sprintf("Executing Ansible Galaxy: %s", command))
cmd := &packer.RemoteCmd{
Command: command,
}
if err := cmd.StartWithUi(comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
// ansible-galaxy version 2.0.0.2 doesn't return exit codes on error..
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus)
}
return nil
}
func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator) error {
playbook := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.PlaybookFile)))
inventory := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.InventoryFile)))
@ -251,6 +298,13 @@ func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator) err
extraArgs = " " + strings.Join(p.config.ExtraArguments, " ")
}
// Fetch external dependencies
if len(p.config.GalaxyFile) > 0 {
if err := p.executeGalaxy(ui, comm); err != nil {
return fmt.Errorf("Error executing Ansible Galaxy: %s", err)
}
}
command := fmt.Sprintf("cd %s && %s %s%s -c local -i %s",
p.config.StagingDir, p.config.Command, playbook, extraArgs, inventory)
ui.Message(fmt.Sprintf("Executing Ansible: %s", command))

View File

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"log"
"net"
"github.com/mitchellh/packer/packer"
@ -100,62 +101,71 @@ func (c *adapter) handleSession(newChannel ssh.NewChannel) error {
for req := range in {
switch req.Type {
case "pty-req":
log.Println("ansible provisioner pty-req request")
// accept pty-req requests, but don't actually do anything. Necessary for OpenSSH and sudo.
req.Reply(true, nil)
case "env":
req.Reply(true, nil)
req, err := newEnvRequest(req)
if err != nil {
c.ui.Error(err.Error())
req.Reply(false, nil)
continue
}
env = append(env, req.Payload)
case "exec":
log.Printf("new env request: %s", req.Payload)
req.Reply(true, nil)
case "exec":
req, err := newExecRequest(req)
if err != nil {
c.ui.Error(err.Error())
req.Reply(false, nil)
close(done)
continue
}
if len(req.Payload) > 0 {
cmd := &packer.RemoteCmd{
Stdin: channel,
Stdout: channel,
Stderr: channel.Stderr(),
Command: string(req.Payload),
}
log.Printf("new exec request: %s", req.Payload)
if err := c.comm.Start(cmd); err != nil {
c.ui.Error(err.Error())
close(done)
return
}
go func(cmd *packer.RemoteCmd, channel ssh.Channel) {
cmd.Wait()
exitStatus := make([]byte, 4)
binary.BigEndian.PutUint32(exitStatus, uint32(cmd.ExitStatus))
channel.SendRequest("exit-status", false, exitStatus)
close(done)
}(cmd, channel)
if len(req.Payload) == 0 {
req.Reply(false, nil)
close(done)
return
}
cmd := &packer.RemoteCmd{
Stdin: channel,
Stdout: channel,
Stderr: channel.Stderr(),
Command: string(req.Payload),
}
if err := c.comm.Start(cmd); err != nil {
c.ui.Error(err.Error())
req.Reply(false, nil)
close(done)
return
}
go func(cmd *packer.RemoteCmd, channel ssh.Channel) {
cmd.Wait()
exitStatus := make([]byte, 4)
binary.BigEndian.PutUint32(exitStatus, uint32(cmd.ExitStatus))
channel.SendRequest("exit-status", false, exitStatus)
close(done)
}(cmd, channel)
req.Reply(true, nil)
case "subsystem":
req, err := newSubsystemRequest(req)
if err != nil {
c.ui.Error(err.Error())
req.Reply(false, nil)
continue
}
log.Printf("new subsystem request: %s", req.Payload)
switch req.Payload {
case "sftp":
c.ui.Say("starting sftp subsystem")
req.Reply(true, nil)
sftpCmd := c.sftpCmd
if len(sftpCmd) == 0 {
sftpCmd = "/usr/lib/sftp-server -e"
@ -167,16 +177,22 @@ func (c *adapter) handleSession(newChannel ssh.NewChannel) error {
Command: sftpCmd,
}
c.ui.Say("starting sftp subsystem")
if err := c.comm.Start(cmd); err != nil {
c.ui.Error(err.Error())
req.Reply(false, nil)
close(done)
return
}
req.Reply(true, nil)
go func() {
cmd.Wait()
close(done)
}()
default:
c.ui.Error(fmt.Sprintf("unsupported subsystem requested: %s", req.Payload))
req.Reply(false, nil)
}
@ -205,6 +221,10 @@ type envRequestPayload struct {
Value string
}
func (p envRequestPayload) String() string {
return fmt.Sprintf("%s=%s", p.Name, p.Value)
}
func newEnvRequest(raw *ssh.Request) (*envRequest, error) {
r := new(envRequest)
r.Request = raw
@ -238,6 +258,10 @@ type execRequest struct {
type execRequestPayload string
func (p execRequestPayload) String() string {
return string(p)
}
func newExecRequest(raw *ssh.Request) (*execRequest, error) {
r := new(execRequest)
r.Request = raw
@ -260,6 +284,10 @@ type subsystemRequest struct {
type subsystemRequestPayload string
func (p subsystemRequestPayload) String() string {
return string(p)
}
func newSubsystemRequest(raw *ssh.Request) (*subsystemRequest, error) {
r := new(subsystemRequest)
r.Request = raw

View File

@ -110,6 +110,10 @@ builder.
launch the resulting AMI(s). By default no additional users other than the
user creating the AMI has permissions to launch it.
- `ami_virtualization_type` (string) - The type of virtualization for the AMI
you are building. This option must match the supported virtualization
type of `source_ami`. Can be "paravirtual" or "hvm".
- `associate_public_ip_address` (boolean) - If using a non-default VPC, public
IP addresses are not provided by default. If this is toggled, your new
instance will get a Public IP.

View File

@ -89,6 +89,7 @@ Packer to work:
``` {.javascript}
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action" : [
@ -114,13 +115,15 @@ Packer to work:
"ec2:DescribeSnapshots",
"ec2:DescribeImages",
"ec2:RegisterImage",
"ec2:DeregisterImage",
"ec2:CreateTags",
"ec2:ModifyImageAttribute",
"ec2:GetPasswordData",
"ec2:DescribeTags",
"ec2:DescribeImageAttribute",
"ec2:CopyImage",
"ec2:DescribeRegions"
"ec2:DescribeRegions",
"ec2:ModifyInstanceAttribute"
],
"Resource" : "*"
}]

View File

@ -74,6 +74,22 @@ straightforwarded, it is documented here.
4. Click "Generate new JSON key" for the Service Account you just created. A
JSON file will be downloaded automatically. This is your *account file*.
### Precedence of Authentication Methods
Packer looks for credentials in the following places, preferring the first location found:
1. A `account_file` option in your packer file.
2. A JSON file (Service Account) whose path is specified by the `GOOGLE_APPLICATION_CREDENTIALS` environment variable.
3. A JSON file in a location known to the `gcloud` command-line tool. (`gcloud` creates it when it's configured)
On Windows, this is: `%APPDATA%/gcloud/application_default_credentials.json`.
On other systems: `$HOME/.config/gcloud/application_default_credentials.json`.
4. On Google Compute Engine and Google App Engine Managed VMs, it fetches credentials from the metadata server. (Needs a correct VM authentication scope configuration, see above)
## Basic Example
Below is a fully functioning example. It doesn't do anything useful, since no

View File

@ -262,6 +262,15 @@ builder and not otherwise conflicting with the qemuargs):
qemu-system-x86 -m 1024m --no-acpi -netdev user,id=mynet0,hostfwd=hostip:hostport-guestip:guestport -device virtio-net,netdev=mynet0"
</pre>
\~&gt; **Windows Users:** [QEMU for Windows](https://qemu.weilnetz.de/) builds are available though an environmental variable does need
to be set for QEMU for Windows to redirect stdout to the console instead of stdout.txt.
The following shows the environment variable that needs to be set for Windows QEMU support:
```json
setx SDL_STDIO_REDIRECT=0
```
You can also use the `SSHHostPort` template variable to produce a packer
template that can be invoked by `make` in parallel:

View File

@ -101,8 +101,44 @@ builder.
for the VM. By default, this is 40000 (about 40 GB).
- `export_opts` (array of strings) - Additional options to pass to the
`VBoxManage export`. This can be useful for passing product information to
include in the resulting appliance file.
[VBoxManage export](https://www.virtualbox.org/manual/ch08.html#vboxmanage-export).
This can be useful for passing product information to include in the
resulting appliance file. Packer JSON configuration file example:
``` {.json}
{
"type": "virtualbox-iso",
"export_opts":
[
"--manifest",
"--vsys", "0",
"--description", "{{user `vm_description`}}",
"--version", "{{user `vm_version`}}"
],
"format": "ova",
}
```
A VirtualBox [VM description](https://www.virtualbox.org/manual/ch08.html#idm3756)
may contain arbitrary strings; the GUI interprets HTML formatting.
However, the JSON format does not allow arbitrary newlines within a
value. Add a multi-line description by preparing the string in the
shell before the packer call like this (shell `>` continuation
character snipped for easier copy & paste):
``` {.shell}
vm_description='some
multiline
description'
vm_version='0.2.0'
packer build \
-var "vm_description=${vm_description}" \
-var "vm_version=${vm_version}" \
"packer_conf.json"
```
- `floppy_files` (array of strings) - A list of files to place onto a floppy
disk that is attached when the VM is booted. This is most useful for

View File

@ -83,8 +83,44 @@ builder.
specified, the default is 10 seconds.
- `export_opts` (array of strings) - Additional options to pass to the
`VBoxManage export`. This can be useful for passing product information to
include in the resulting appliance file.
[VBoxManage export](https://www.virtualbox.org/manual/ch08.html#vboxmanage-export).
This can be useful for passing product information to include in the
resulting appliance file. Packer JSON configuration file example:
``` {.json}
{
"type": "virtualbox-ovf",
"export_opts":
[
"--manifest",
"--vsys", "0",
"--description", "{{user `vm_description`}}",
"--version", "{{user `vm_version`}}"
],
"format": "ova",
}
```
A VirtualBox [VM description](https://www.virtualbox.org/manual/ch08.html#idm3756)
may contain arbitrary strings; the GUI interprets HTML formatting.
However, the JSON format does not allow arbitrary newlines within a
value. Add a multi-line description by preparing the string in the
shell before the packer call like this (shell `>` continuation
character snipped for easier copy & paste):
``` {.shell}
vm_description='some
multiline
description'
vm_version='0.2.0'
packer build \
-var "vm_description=${vm_description}" \
-var "vm_version=${vm_version}" \
"packer_conf.json"
```
- `floppy_files` (array of strings) - A list of files to place onto a floppy
disk that is attached when the VM is booted. This is most useful for

View File

@ -22,8 +22,13 @@ continuing. This will allow you to inspect state and so on.
In debug mode once the remote instance is instantiated, Packer will emit to the
current directory an ephemeral private ssh key as a .pem file. Using that you
can `ssh -i <key.pem>` into the remote build instance and see what is going on
for debugging. The ephemeral key will be deleted at the end of the packer run
during cleanup.
for debugging. The key will only be emitted for cloud-based builders. The
ephemeral key will be deleted at the end of the packer run during cleanup.
For a local builder, the SSH session initiated will be visible in the detail
provided when `PACKER_LOG=1` environment variable is set prior to a build,
and you can connect to the local machine using the userid and password defined
in the kickstart or preseed associated with initialzing the local VM.
### Windows

View File

@ -107,6 +107,11 @@ chi-appservers
your local system. These will be uploaded to the remote machine under
`staging_directory`/playbooks. By default, this is empty.
- `galaxy_file` (string) - A requirements file which provides a way to install
roles with the [ansible-galaxy
cli](http://docs.ansible.com/ansible/galaxy.html#the-ansible-galaxy-command-line-tool)
on the remote machine. By default, this is empty.
- `group_vars` (string) - a path to the directory containing ansible group
variables on your local system to be copied to the remote machine. By
default, this is empty.

View File

@ -68,7 +68,7 @@ listed below:
- `puppet_server` (string) - Hostname of the Puppet server. By default
"puppet" will be used.
- `staging_directory` (string) - This is the directory where all the
- `staging_dir` (string) - This is the directory where all the
configuration of Puppet by Packer will be placed. By default this
is "/tmp/packer-puppet-server". This directory doesn't need to exist but
must have proper permissions so that the SSH user that Packer uses is able

View File

@ -113,6 +113,9 @@ With a properly validated template. It is time to build your first image. This
is done by calling `packer build` with the template file. The output should look
similar to below. Note that this process typically takes a few minutes.
-&gt; **Note:** When using packer on Windows, replace the single-quotes in the
command below with double-quotes.
``` {.text}
$ packer build \
-var 'aws_access_key=YOUR ACCESS KEY' \