Merge branch 'master' into configure-manifest-dir

This commit is contained in:
Jan Brauer 2013-12-10 09:08:11 +01:00
commit afd0aea8df
29 changed files with 237 additions and 141 deletions

View File

@ -1,9 +1,31 @@
## 0.4.1 (unreleased)
## 0.5.0 (unreleased)
## 0.4.1 (December 7, 2013)
IMPROVEMENTS:
* builder/amazon/ebs: New option allows associating a public IP with
non-default VPC instances. [GH-660]
* builder/openstack: A "proxy\_url" setting was added to define an HTTP
proxy to use when building with this builder. [GH-637]
BUG FIXES:
* core: Don't change background color on CLI anymore, making things look
a tad nicer in some terminals.
* core: multiple ISO URLs works properly in all builders. [GH-683]
* builder/amazon/chroot: Block when obtaining file lock to allow
parallel builds. [GH-689]
* builder/amazon/instance: Add location flag to upload bundle command
so that building AMIs works out of us-east-1 [GH-679]
* builder/vmware: Cleanup of VMX keys works properly so cd-rom won't
get stuck with ISO. [GH-685]
* builder/vmware: File cleanup is more resilient to file delete races
with the operating system. [GH-675]
* provisioner/puppet-masterless: Check for hiera config path existence
properly. [GH-656]
## 0.4.0 (November 19, 2013)

View File

@ -77,7 +77,7 @@ For some additional dependencies, Go needs [Mercurial](http://mercurial.selenic.
and [Bazaar](http://bazaar.canonical.com/en/) to be installed.
Packer itself doesn't require these, but a dependency of a dependency does.
You'll also need [`gox`](https://github.com/mitchellh/packer)
You'll also need [`gox`](https://github.com/mitchellh/gox)
to compile packer. You can install that with:
```

View File

@ -3,7 +3,6 @@
package chroot
import (
"errors"
"os"
"syscall"
)
@ -14,13 +13,8 @@ const LOCK_NB = 4
const LOCK_UN = 8
func lockFile(f *os.File) error {
err := syscall.Flock(int(f.Fd()), LOCK_EX|LOCK_NB)
err := syscall.Flock(int(f.Fd()), LOCK_EX)
if err != nil {
errno, ok := err.(syscall.Errno)
if ok && errno == syscall.EWOULDBLOCK {
return errors.New("file already locked")
}
return err
}

View File

@ -39,7 +39,7 @@ func (s *StepFlock) Run(state multistep.StateBag) multistep.StepAction {
// LOCK!
if err := lockFile(f); err != nil {
err := fmt.Errorf("Error creating lock: %s", err)
err := fmt.Errorf("Error obtaining lock: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt

View File

@ -11,20 +11,21 @@ import (
// RunConfig contains configuration for running an instance from a source
// AMI and details on how to access that launched image.
type RunConfig struct {
SourceAmi string `mapstructure:"source_ami"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceType string `mapstructure:"instance_type"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
VpcId string `mapstructure:"vpc_id"`
AvailabilityZone string `mapstructure:"availability_zone"`
SourceAmi string `mapstructure:"source_ami"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceType string `mapstructure:"instance_type"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"`
SubnetId string `mapstructure:"subnet_id"`
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
VpcId string `mapstructure:"vpc_id"`
AvailabilityZone string `mapstructure:"availability_zone"`
// Unexported fields that are calculated from others
sshTimeout time.Duration

View File

@ -20,7 +20,11 @@ func SSHAddress(e *ec2.EC2, port int) func(multistep.StateBag) (string, error) {
if i.DNSName != "" {
host = i.DNSName
} else if i.VpcId != "" {
host = i.PrivateIpAddress
if i.PublicIpAddress != "" {
host = i.PublicIpAddress
} else {
host = i.PrivateIpAddress
}
}
if host != "" {

View File

@ -10,16 +10,17 @@ import (
)
type StepRunSourceInstance struct {
Debug bool
ExpectedRootDevice string
InstanceType string
UserData string
UserDataFile string
SourceAMI string
IamInstanceProfile string
SubnetId string
AvailabilityZone string
BlockDevices BlockDevices
Debug bool
ExpectedRootDevice string
InstanceType string
UserData string
UserDataFile string
SourceAMI string
IamInstanceProfile string
SubnetId string
AssociatePublicIpAddress bool
AvailabilityZone string
BlockDevices BlockDevices
instance *ec2.Instance
}
@ -47,17 +48,18 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
}
runOpts := &ec2.RunInstances{
KeyName: keyName,
ImageId: s.SourceAMI,
InstanceType: s.InstanceType,
UserData: []byte(userData),
MinCount: 0,
MaxCount: 0,
SecurityGroups: securityGroups,
IamInstanceProfile: s.IamInstanceProfile,
SubnetId: s.SubnetId,
BlockDevices: s.BlockDevices.BuildLaunchDevices(),
AvailZone: s.AvailabilityZone,
KeyName: keyName,
ImageId: s.SourceAMI,
InstanceType: s.InstanceType,
UserData: []byte(userData),
MinCount: 0,
MaxCount: 0,
SecurityGroups: securityGroups,
IamInstanceProfile: s.IamInstanceProfile,
SubnetId: s.SubnetId,
AssociatePublicIpAddress: s.AssociatePublicIpAddress,
BlockDevices: s.BlockDevices.BuildLaunchDevices(),
AvailZone: s.AvailabilityZone,
}
ui.Say("Launching a source AWS instance...")
@ -114,6 +116,10 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
ui.Message(fmt.Sprintf("Public DNS: %s", s.instance.DNSName))
}
if s.instance.PublicIpAddress != "" {
ui.Message(fmt.Sprintf("Public IP: %s", s.instance.PublicIpAddress))
}
if s.instance.PrivateIpAddress != "" {
ui.Message(fmt.Sprintf("Private IP: %s", s.instance.PrivateIpAddress))
}

View File

@ -93,16 +93,17 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VpcId: b.config.VpcId,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
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,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
},
&common.StepConnectSSH{
SSHAddress: awscommon.SSHAddress(ec2conn, b.config.SSHPort),

View File

@ -74,6 +74,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"-s {{.SecretKey}} " +
"-d {{.BundleDirectory}} " +
"--batch " +
"--location {{.Region}} " +
"--retry"
}
@ -196,16 +197,17 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VpcId: b.config.VpcId,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "instance-store",
InstanceType: b.config.InstanceType,
IamInstanceProfile: b.config.IamInstanceProfile,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
SubnetId: b.config.SubnetId,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Debug: b.config.PackerDebug,
ExpectedRootDevice: "instance-store",
InstanceType: b.config.InstanceType,
IamInstanceProfile: b.config.IamInstanceProfile,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
},
&common.StepConnectSSH{
SSHAddress: awscommon.SSHAddress(ec2conn, b.config.SSHPort),

View File

@ -11,6 +11,7 @@ type uploadCmdData struct {
BucketName string
BundleDirectory string
ManifestPath string
Region string
SecretKey string
}
@ -23,12 +24,20 @@ func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
manifestPath := state.Get("manifest_path").(string)
ui := state.Get("ui").(packer.Ui)
var err error
region, err := config.Region()
if err != nil {
err := fmt.Errorf("Error retrieving region: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
config.BundleUploadCommand, err = config.tpl.Process(config.BundleUploadCommand, uploadCmdData{
AccessKey: config.AccessKey,
BucketName: config.S3Bucket,
BundleDirectory: config.BundleDestination,
ManifestPath: manifestPath,
Region: region.Name,
SecretKey: config.SecretKey,
})
if err != nil {

View File

@ -4,6 +4,8 @@ import (
"fmt"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"net/http"
"net/url"
"os"
)
@ -14,6 +16,7 @@ type AccessConfig struct {
Project string `mapstructure:"project"`
Provider string `mapstructure:"provider"`
RawRegion string `mapstructure:"region"`
ProxyUrl string `mapstructure:"proxy_url"`
}
// Auth returns a valid Auth object for access to openstack services, or
@ -23,6 +26,7 @@ func (c *AccessConfig) Auth() (gophercloud.AccessProvider, error) {
password := c.Password
project := c.Project
provider := c.Provider
proxy := c.ProxyUrl
if username == "" {
username = os.Getenv("SDK_USERNAME")
@ -47,6 +51,19 @@ func (c *AccessConfig) Auth() (gophercloud.AccessProvider, error) {
authoptions.TenantName = project
}
// For corporate networks it may be the case where we want our API calls
// to be sent through a separate HTTP proxy than external traffic.
if proxy != "" {
url, err := url.Parse(proxy)
if err != nil {
return nil, err
}
// The gophercloud.Context has a UseCustomClient method which
// would allow us to override with a new instance of http.Client.
http.DefaultTransport = &http.Transport{Proxy: http.ProxyURL(url)}
}
return gophercloud.Authenticate(provider, authoptions)
}

View File

@ -217,7 +217,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
if !(b.config.Format == "qcow2" || b.config.Format == "raw") {
errs = packer.MultiErrorAppend(
errs, errors.New("invalid format, only 'qcow2' or 'img' are allowed"))
errs, errors.New("invalid format, only 'qcow2' or 'raw' are allowed"))
}
if !(b.config.Accelerator == "kvm" || b.config.Accelerator == "xen") {

View File

@ -314,7 +314,8 @@ func (d *ESX5Driver) checkGuestIPHackEnabled() error {
}
if record["IntValue"] != "1" {
return errors.New("GuestIPHack is required, enable with:\n" +
return errors.New(
"GuestIPHack is required, enable by running this on the ESX machine:\n" +
"esxcli system settings advanced set -o /Net/GuestIPHack -i 1")
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
"path/filepath"
)
@ -48,8 +49,11 @@ func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction {
if !keep {
ui.Message(fmt.Sprintf("Deleting: %s", path))
if err = dir.Remove(path); err != nil {
state.Put("error", err)
return multistep.ActionHalt
// Only report the error if the file still exists
if _, serr := os.Stat(path); serr == nil || !os.IsNotExist(serr) {
state.Put("error", err)
return multistep.ActionHalt
}
}
}
}

View File

@ -55,12 +55,12 @@ func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
continue
}
filenameKey := match + ".filename"
filenameKey := match + "filename"
if filename, ok := vmxData[filenameKey]; ok {
if filename == isoPath {
// Change the CD-ROM device back to auto-detect to eject
vmxData[filenameKey] = "auto detect"
vmxData[match+".devicetype"] = "cdrom-raw"
vmxData[match+"devicetype"] = "cdrom-raw"
}
}
}

View File

@ -105,21 +105,21 @@ func DownloadableURL(original string) (string, error) {
url.Path = strings.Replace(url.Path, `\`, `/`, -1)
}
if _, err := os.Stat(url.Path); err != nil {
return "", err
}
// Only do the filepath transformations if the file appears
// to actually exist.
if _, err := os.Stat(url.Path); err == nil {
url.Path, err = filepath.Abs(url.Path)
if err != nil {
return "", err
}
url.Path, err = filepath.Abs(url.Path)
if err != nil {
return "", err
}
url.Path, err = filepath.EvalSymlinks(url.Path)
if err != nil {
return "", err
}
url.Path, err = filepath.EvalSymlinks(url.Path)
if err != nil {
return "", err
url.Path = filepath.Clean(url.Path)
}
url.Path = filepath.Clean(url.Path)
}
// Make sure it is lowercased

View File

@ -144,8 +144,8 @@ func TestDownloadableURL_FilePaths(t *testing.T) {
for _, prefix := range []string{"", "file://"} {
// Nonexistent file
_, err = DownloadableURL(prefix + "i/dont/exist")
if err == nil {
t.Fatal("expected err")
if err != nil {
t.Fatalf("err: %s", err)
}
// Good file

View File

@ -10,7 +10,7 @@ import (
var GitCommit string
// The version of packer.
const Version = "0.4.1"
const Version = "0.5.0"
// Any pre-release marker for the version. If this is "" (empty string),
// then it means that it is a final release. Otherwise, this is the

View File

@ -152,7 +152,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
// Validation
if p.config.HieraConfigPath != "" {
info, err := os.Stat(p.config.ManifestFile)
info, err := os.Stat(p.config.HieraConfigPath)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("hiera_config_path is invalid: %s", err))

View File

@ -210,40 +210,3 @@ prevent packages installed by your provisioners from starting services:
]
}
</pre>
## Using an IAM Instance Profile
If AWS keys are not specified in the template or through environment variables
Packer will use credentials provided by the instance's IAM profile, if it has one.
The following policy document provides the minimal set permissions necessary for Packer to work:
<pre class="prettyprint">
{
"Statement": [{
"Effect": "Allow",
"Action" : [
"ec2:AttachVolume",
"ec2:CreateVolume",
"ec2:DeleteVolume",
"ec2:DescribeVolumes",
"ec2:DetachVolume",
"ec2:DescribeInstances",
"ec2:CreateSnapshot",
"ec2:DeleteSnapshot",
"ec2:DescribeSnapshots",
"ec2:DescribeImages",
"ec2:RegisterImage",
"ec2:CreateTags"
],
"Resource" : "*"
}]
}
</pre>
Depending on what setting you use the following Actions might have to be allowed as well:
* `ec2:ModifyImageAttribute` when using `ami_description`

View File

@ -111,6 +111,10 @@ Optional:
* `subnet_id` (string) - If using VPC, the ID of the subnet, such as
"subnet-12345def", where Packer will launch the EC2 instance.
* `associate_public_ip_address` (bool) - 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.
* `tags` (object of key/value strings) - Tags applied to the AMI.
* `user_data` (string) - User data to apply when launching the instance.

View File

@ -150,6 +150,10 @@ Optional:
* `subnet_id` (string) - If using VPC, the ID of the subnet, such as
"subnet-12345def", where Packer will launch the EC2 instance.
* `associate_public_ip_address` (bool) - 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.
* `tags` (object of key/value strings) - Tags applied to the AMI.
* `user_data` (string) - User data to apply when launching the instance.
@ -269,6 +273,7 @@ sudo -n ec2-upload-bundle \
-s {{.SecretKey}} \
-d {{.BundleDirectory}} \
--batch \
--location {{.Region}} \
--retry
```

View File

@ -30,3 +30,40 @@ AMI. Packer supports the following builders at the moment:
<a href="/docs/builders/amazon-ebs.html">amazon-ebs builder</a>. It is
much easier to use and Amazon generally recommends EBS-backed images nowadays.
</div>
## Using an IAM Instance Profile
If AWS keys are not specified in the template or through environment variables
Packer will use credentials provided by the instance's IAM profile, if it has one.
The following policy document provides the minimal set permissions necessary for Packer to work:
<pre class="prettyprint">
{
"Statement": [{
"Effect": "Allow",
"Action" : [
"ec2:AttachVolume",
"ec2:CreateVolume",
"ec2:DeleteVolume",
"ec2:DescribeVolumes",
"ec2:DetachVolume",
"ec2:DescribeInstances",
"ec2:CreateSnapshot",
"ec2:DeleteSnapshot",
"ec2:DescribeSnapshots",
"ec2:DescribeImages",
"ec2:RegisterImage",
"ec2:CreateTags"
],
"Resource" : "*"
}]
}
</pre>
Depending on what setting you use the following Actions might have to be allowed as well:
* `ec2:ModifyImageAttribute` when using `ami_description`

View File

@ -59,7 +59,7 @@ Optional:
* `ssh_timeout` (string) - The time to wait for SSH to become available
before timing out. The format of this value is a duration such as "5s"
or "5m". The default SSH timeout is "1m".
or "1m". The default SSH timeout is "5m".
* `ssh_username` (string) - The username to use in order to communicate
over SSH to the running server. The default is "root".

View File

@ -115,7 +115,7 @@ Optional:
commands or kickstart type scripts must have proper adjustments for
resulting device names. The Qemu builder uses "virtio" by default.
* `format` (string) - Either "qcow2" or "img", this specifies the output
* `format` (string) - Either "qcow2" or "raw", this specifies the output
format of the virtual machine image. This defaults to "qcow2".
* `headless` (bool) - Packer defaults to building virtual machines by

View File

@ -87,7 +87,8 @@ Optional:
The default is "1", which corresponds to a growable virtual disk split in
2GB files. This option is for advanced usage, modify only if you
know what you're doing. For more information, please consult the
[Virtual Disk Manager User's Guide](http://www.vmware.com/pdf/VirtualDiskManager.pdf).
[Virtual Disk Manager User's Guide](http://www.vmware.com/pdf/VirtualDiskManager.pdf)
for desktop VMware clients. For ESXi, refer to the proper ESXi documentation.
* `floppy_files` (array of strings) - A list of files to put onto a floppy
disk that is attached when the VM is booted for the first time. This is

View File

@ -46,10 +46,14 @@ briefly. Create a file `example.json` and fill it with the following contents:
<pre class="prettyprint">
{
"variables": {
"aws_access_key": "",
"aws_secret_key": ""
},
"builders": [{
"type": "amazon-ebs",
"access_key": "YOUR KEY HERE",
"secret_key": "YOUR SECRET KEY HERE",
"access_key": "{{user `aws_access_key`}}",
"secret_key": "{{user `aws_secret_key`}}",
"region": "us-east-1",
"source_ami": "ami-de0d9eb7",
"instance_type": "t1.micro",
@ -59,9 +63,11 @@ briefly. Create a file `example.json` and fill it with the following contents:
}
</pre>
Please fill in the `access_key` and `secret_key` with the proper values
for your account. Your security credentials can be found on
[this page](https://console.aws.amazon.com/iam/home?#security_credential).
When building, you'll pass in the `aws_access_key` and `aws_access_key` as
a [user variable](/docs/templates/user-variables.html), keeping your secret
keys out of the template. You can create security credentials
on [this page](https://console.aws.amazon.com/iam/home?#security_credential).
An example IAM policy document can be found in the [Amazon EC2 builder docs](/docs/builders/amazon.html).
This is a basic template that is ready-to-go. It should be immediately recognizable
as a normal, basic JSON object. Within the object, the `builders` section
@ -106,7 +112,10 @@ should look similar to below. Note that this process typically takes a
few minutes.
```
$ packer build example.json
$ packer build \
-var 'aws_access_key=YOUR ACCESS KEY' \
-var 'aws_secret_key=YOUR SECRET KEY' \
example.json
==> amazon-ebs: amazon-ebs output will be in this color.
==> amazon-ebs: Creating temporary keypair for this instance...

View File

@ -63,13 +63,23 @@ array.
<pre class="prettyprint">
{
"type": "digitalocean",
"api_key": "INSERT API KEY HERE",
"client_id": "INSERT CLIENT ID HERE"
"api_key": "{{user `do_api_key`}}",
"client_id": "{{user `do_client_id`}}"
}
</pre>
Fill in your `api_key` and `client_id` for DigitalOcean as necessary.
The entire template should now [look like this](https://gist.github.com/mitchellh/51a447e38e7e496eb29c).
You'll also need to modify the `variables` section of the template
to include the access keys for DigitalOcean.
<pre class="prettyprint">
"variables": {
...
"do_api_key": "",
"do_client_id": ""
}
</pre>
The entire template should now [look like this](https://gist.github.com/pearkes/cc5f8505eee5403a43a6).
Additional builders are simply added to the `builders` array in the template.
This tells Packer to build multiple images. The builder `type` values don't
@ -87,13 +97,18 @@ manual that contains a listing of all the available configuration options.
## Build
Now run `packer build example.json`. The output is too verbose to include
Now run `packer build` with your user variables. The output is too verbose to include
all of it, but a portion of it is reproduced below. Note that the ordering
and wording of the lines may be slightly different, but the effect is the
same.
```
$ packer build example.json
$ packer build \
-var 'aws_access_key=YOUR ACCESS KEY' \
-var 'aws_secret_key=YOUR SECRET KEY' \
-var 'do_api_key=YOUR API KEY' \
-var 'do_client_id=YOUR CLIENT ID' \
example.json
==> amazon-ebs: amazon-ebs output will be in this color.
==> digitalocean: digitalocean output will be in this color.

View File

@ -37,6 +37,7 @@ block below.
<pre class="prettyprint">
{
"variables": [...],
"builders": [...],
"provisioners": [{