Compare commits

..

115 Commits

Author SHA1 Message Date
Mitchell Hashimoto 0eb9e06637 v0.3.0 2013-08-12 13:57:04 -07:00
Mitchell Hashimoto e00f114520 builder/amazon/common: correct logic in deleting secutiry group 2013-08-12 13:43:52 -07:00
Mitchell Hashimoto c09c1108ca builder/amazon/common: retry deleting security group [GH-278] 2013-08-12 13:34:55 -07:00
Mitchell Hashimoto 34556713dc website: document user data stuff 2013-08-12 12:12:44 -07:00
Mitchell Hashimoto 4d7b0c2dbc builder/amazon/all: support user data file 2013-08-12 11:53:50 -07:00
Mitchell Hashimoto c1f8e5c3b3 builder/amazon/common: support user_data_file 2013-08-12 11:52:43 -07:00
Mitchell Hashimoto 97b4c7d22b Update CHANGELOG 2013-08-12 11:40:59 -07:00
Mitchell Hashimoto 4e300259c7 Merge pull request #253 from phobos182/master
builder/amazon/common: added passing UserData to goamz.
2013-08-12 11:36:15 -07:00
Mitchell Hashimoto db903af566 website: document machine-readable types 2013-08-12 11:33:57 -07:00
Mitchell Hashimoto 3c9e504475 command/build: use the real artifact string in machine readable 2013-08-12 10:31:36 -07:00
Mitchell Hashimoto d88ff24343 packer/rpc: ui.Machine works over RPC properly 2013-08-12 10:25:56 -07:00
Mitchell Hashimoto f7bd403151 command/build: machine-readable artifacts 2013-08-12 10:09:45 -07:00
Mitchell Hashimoto 1bf2bade7d packer: machine readable output for available commands 2013-08-12 09:22:01 -07:00
Mitchell Hashimoto d4d58e3779 Fix packer test 2013-08-12 09:19:24 -07:00
Mitchell Hashimoto 4f58b20479 provisioner/salt-masterless: nice error even if bad exit status
/cc @rgarcia
2013-08-12 09:19:11 -07:00
Mitchell Hashimoto 758a53b146 scripts: full SHA1 in build, not short 2013-08-12 09:17:03 -07:00
Mitchell Hashimoto 336495d34c Update CHANGELOG 2013-08-12 00:02:25 -07:00
Mitchell Hashimoto 78d03c58e5 Merge branch 'machine-readable'
This adds the -machine-readable flag to Packer which turns all output
into machine-readable format. This is documented within the website
source.
2013-08-12 00:01:18 -07:00
Mitchell Hashimoto 43b23c5221 packer: version machine-readable output 2013-08-11 23:56:47 -07:00
Mitchell Hashimoto 226e6f5859 website: document machine-readable 2013-08-11 23:56:39 -07:00
Mitchell Hashimoto bebed45a80 packer: -machine-readable not double hyphen 2013-08-11 23:26:24 -07:00
Mitchell Hashimoto f4a9d9d3b7 packer: document machine-readable 2013-08-11 23:24:57 -07:00
Mitchell Hashimoto eed6ece45b packer: replace \r with literal on Ui for MR 2013-08-11 23:19:13 -07:00
Mitchell Hashimoto 04c8592077 packer: replace new lines with literal \n 2013-08-11 23:18:14 -07:00
Mitchell Hashimoto f3bb028ec3 packer: output a newline after each MR line 2013-08-11 23:14:42 -07:00
Mitchell Hashimoto f46bf47579 parse --machine-readable and set it 2013-08-11 23:12:20 -07:00
Mitchell Hashimoto ca54df1a76 packer: TargettedUi properly targets machine-readable data 2013-08-11 19:08:08 -07:00
Mitchell Hashimoto b54a5c0074 packer: Add MachineReadableUi 2013-08-11 19:05:07 -07:00
Mitchell Hashimoto cd62aa5003 packer: BasicUi doesn't do machine-readable, just log 2013-08-11 18:38:24 -07:00
Mitchell Hashimoto 10b0f920bc packer: renamed PrefixedUi to TargettedUi 2013-08-11 18:31:28 -07:00
Mitchell Hashimoto f3f4ab91c7 packer: Rename ReaderWriterUi to BasicUi 2013-08-11 18:20:27 -07:00
Mitchell Hashimoto c8bc2bc1ba packer: Add Machine func to Ui 2013-08-11 18:16:00 -07:00
Mitchell Hashimoto 41370a215e Update CHANGELOG 2013-08-11 16:18:40 -07:00
Mitchell Hashimoto f2355a577a fmt 2013-08-11 16:17:59 -07:00
Mitchell Hashimoto 6a7450974d Merge pull request #269 from rach/master
provisioner/salt-masterless: `local_state_tree` should not be mandatory
2013-08-11 16:17:37 -07:00
Mitchell Hashimoto 810bea7fcb communicator/ssh: docs 2013-08-11 16:15:10 -07:00
Mitchell Hashimoto d5ab581260 Merge pull request #273 from rgarcia/password-key
communicator/ssh: ability to use a password-protected key
2013-08-11 16:13:53 -07:00
Rafael Garcia ae35cd51e2 communicator/ssh: ability to use a password-protected key 2013-08-11 09:46:28 -07:00
Rach Belaid a4c555a5b7 #269 Make local_state_tree option not mandatory
Update and rename salt.html.markdown to salt-masterless.html.markdown
salt.html.markdown had a better history and formatting
And remove local_state_tree from required option from code and docs
2013-08-10 16:18:57 +01:00
Rach Belaid a542ff967b Delete duplicate doc for Salt masterless provisionner
there is a other doc file for this called salt.html.markdown which contain a better history
2013-08-10 15:24:04 +01:00
Mitchell Hashimoto 59eff6a222 website: add GA tracker 2013-08-09 18:12:11 -07:00
Mitchell Hashimoto b25f0ceabf provisioner/salt-masterless: error if any commands exit with non-zero
[GH-266]

/cc @rgarcia
2013-08-09 17:35:57 -07:00
Mitchell Hashimoto a0a6817f21 Update CHANGELOG 2013-08-09 17:33:22 -07:00
Mitchell Hashimoto 5decc186a6 builder/vmware: nitpick some styles
/cc @rasa - I changed up quite a bit here. I tried to reduce function
count if possible, renamed some functions, etc. Overall the
functionality was all spot on, but I felt the functions were too
specialized. Thanks!
2013-08-09 17:31:43 -07:00
Ross Smith II cfc7715495 fixed typo in vmware path on Windows XP, fixes #237/#238 2013-08-09 17:10:05 -07:00
Ross Smith II 60fc565dbb Add support for VMWare Workstation on Windows XP, fixes #237 2013-08-09 17:09:55 -07:00
Mitchell Hashimoto da4a7c3b70 provisioner/salt-masterless: don't delete states [GH-265] 2013-08-09 17:01:39 -07:00
Mitchell Hashimoto 2349706a68 packer/rpc: register map[string]string since we transfer that 2013-08-09 16:53:18 -07:00
Mitchell Hashimoto 777f595c8b common/json: add Unmarshal with method with syntax errors 2013-08-09 16:50:24 -07:00
Mitchell Hashimoto e7b1ba2f5e command/bulid: cosmetic, align help text 2013-08-09 16:46:38 -07:00
Mitchell Hashimoto 95926424f2 command/build,validate: accept -var-file 2013-08-09 16:46:06 -07:00
Mitchell Hashimoto 1e4e343333 common/command: add -var-file support for user vars 2013-08-09 16:45:29 -07:00
Mitchell Hashimoto de13474865 website: document user variables 2013-08-09 16:25:13 -07:00
Mitchell Hashimoto 3ca085c2c1 Update CHANGELOG 2013-08-09 15:57:09 -07:00
Mitchell Hashimoto 713791ba9a command/build,validate: pass user vars to Prepare 2013-08-09 15:57:09 -07:00
Mitchell Hashimoto bb1b3d8fe0 common/command: parse the "-var" flag 2013-08-09 15:57:08 -07:00
Mitchell Hashimoto 1a1b4ba715 common/command: rename BuildFilters to BuildOptions 2013-08-09 15:57:08 -07:00
Mitchell Hashimoto 41b3f9ebd9 command/build,validate: use new ParseTemplateFile methods 2013-08-09 15:57:08 -07:00
Mitchell Hashimoto d0200b7be8 command/validate: accept -only/-except and use new common stuff 2013-08-09 15:57:08 -07:00
Mitchell Hashimoto a0033fba35 common/command: introduce this package for common cmmand stuff 2013-08-09 15:57:08 -07:00
Mitchell Hashimoto 180d5c8275 packer: add ParseTemplateFile since that is useful 2013-08-09 15:57:08 -07:00
Mitchell Hashimoto ddfd9be8cc command/build,validate: compiles again with latest user var changes 2013-08-09 15:57:08 -07:00
Mitchell Hashimoto bf457f437c provisioner/salt-masterless: convert to new template stuff + user vars 2013-08-09 15:57:08 -07:00
Mitchell Hashimoto 100458955f builder/*, provisioner/*: set user vars 2013-08-09 15:57:08 -07:00
Mitchell Hashimoto ab5a1981d9 common: UserData => UserVars 2013-08-09 15:57:08 -07:00
Mitchell Hashimoto eaf5e66f3a common: PackerConfig understands user vars 2013-08-09 15:57:08 -07:00
Mitchell Hashimoto 3e8b3b9327 packer: properly override/send user variables to prepare 2013-08-09 15:57:08 -07:00
Mitchell Hashimoto 7408558a55 packer, packer/rpc: Update Build interface to allow variable overrides 2013-08-09 15:57:07 -07:00
Mitchell Hashimoto d87f878a44 packer: Template understands variables, puts it into a Build 2013-08-09 15:57:07 -07:00
Mitchell Hashimoto 7836d60c61 Merge pull request #259 from phobos182/debug
builder/amazon/common: Log error messages from goamz on state change errors
2013-08-09 11:58:24 -07:00
Jeremy Carroll c94287bbde Log error messages from goamz. 2013-08-09 11:55:34 -07:00
Mitchell Hashimoto f909bc29ed website: update docs for image attributes
/cc @jmassara - easy stuff :)
2013-08-08 22:57:22 -07:00
Mitchell Hashimoto d736c4b180 Update CHANGELOG 2013-08-08 22:53:33 -07:00
Mitchell Hashimoto afe8dda7a9 builder/amazon/chroot: can modify AMI attributes 2013-08-08 22:52:39 -07:00
Mitchell Hashimoto 558f919e4f builder/amazon/common: rename StepModifyAttributes to
StepModifyAMIAttributes
2013-08-08 22:51:48 -07:00
Mitchell Hashimoto d7c9658c1c builder/amazon/common: AMIConfig common config
/cc @jmassara - I pulled out the AMI stuff into a common config
struct and also added in the new template stuff (that didn't exist
when you made the pull, so not your fault! :))
2013-08-08 22:50:37 -07:00
Mitchell Hashimoto 2a0b3cfdc2 builder/amazon/common: on modify AMI attributes, skip if no work
/cc @jmassara
2013-08-08 22:46:50 -07:00
Mitchell Hashimoto 1f53e047a3 builder/amazon/common: use new API for goamz to set AMI attributes
/cc @jmassara
2013-08-08 22:43:29 -07:00
James Massara b0394c6d30 builder/amazon/all: Added support for setting attributes on the AMI 2013-08-08 22:35:21 -07:00
Mitchell Hashimoto d4d7730473 Merge branch 'ignored_files' of https://github.com/priteau/packer into priteau-ignored_files
Conflicts:
	website/source/docs/builders/amazon-instance.html.markdown
2013-08-08 17:30:35 -07:00
Mitchell Hashimoto 9dc98a6a2a Update CHANGELOG 2013-08-08 17:26:46 -07:00
Mitchell Hashimoto ffc73a2ffa command/fix: replace CreateTime with new timestamp 2013-08-08 17:25:26 -07:00
Mitchell Hashimoto db5b236ad6 command/fix: log fixer to run 2013-08-08 17:25:26 -07:00
Mitchell Hashimoto bdd49619ae command/fix: use strings instead of Fixer for list 2013-08-08 17:25:26 -07:00
Mitchell Hashimoto 23803621e9 provisioner/shell: tests passing and compiling 2013-08-08 17:25:26 -07:00
Mitchell Hashimoto 9520c6e6ed builder/amazon: more efficient use of templates 2013-08-08 17:25:26 -07:00
Mitchell Hashimoto c6e36108b3 README: move CreateTime to timestmap 2013-08-08 17:25:26 -07:00
Mitchell Hashimoto a9df9e91c6 website: get rid of CreateTime for timestamp 2013-08-08 17:25:26 -07:00
Mitchell Hashimoto f9ffb1fc54 website: document the new createtime stuff 2013-08-08 17:25:26 -07:00
Mitchell Hashimoto 16ccfe3824 website: new config template docs 2013-08-08 17:25:26 -07:00
Mitchell Hashimoto 1b01e82d8d provisioner/shell: convert to latest template stuff 2013-08-08 17:25:26 -07:00
Mitchell Hashimoto f66c145ee8 provisioner/file: use the template processing stuff 2013-08-08 17:25:26 -07:00
Mitchell Hashimoto ad9575a041 builder/vmware: error checking on upload path template 2013-08-08 17:25:25 -07:00
Mitchell Hashimoto d71621a599 builder/vmware: use new template stuff 2013-08-08 17:25:25 -07:00
Mitchell Hashimoto 8fad60b1b4 builder/virtualbox: use new template processing stuff 2013-08-08 17:25:25 -07:00
Mitchell Hashimoto fde7910e85 builder/amazon: remove unnecessary validation 2013-08-08 17:25:25 -07:00
Mitchell Hashimoto 66818ef89f builder/digitalocean: use new template processing 2013-08-08 17:25:25 -07:00
Mitchell Hashimoto 1268373105 builder/amazon/instance: switch to new template stuff 2013-08-08 17:25:25 -07:00
Mitchell Hashimoto a9b951e1fe builder/amazon/ebs: get rid of unnecessary template 2013-08-08 17:25:25 -07:00
Mitchell Hashimoto 28e72c7f7b builder/amazon/chroot: switch to new template stuff 2013-08-08 17:25:25 -07:00
Mitchell Hashimoto a669b4dbd0 builder/amazon/ebs: process templates for config 2013-08-08 17:25:25 -07:00
Mitchell Hashimoto 8e7aa41bdf builder/amazon/common: RunConfig to template processing 2013-08-08 17:25:25 -07:00
Mitchell Hashimoto 93ac76e9c7 builder/amazon/common: access config uses template processing 2013-08-08 17:25:25 -07:00
Mitchell Hashimoto ff27abc06a common: clone template during validation to avoid unnecessary children 2013-08-08 17:25:24 -07:00
Mitchell Hashimoto 04a378c39a common: validation and generating unique template names 2013-08-08 17:25:24 -07:00
Mitchell Hashimoto ecd598ab64 common: support user data 2013-08-08 17:25:24 -07:00
Mitchell Hashimoto 915f9b73be common: functions for template processing 2013-08-08 17:25:24 -07:00
Pierre Riteau cb756912ad Warn that ec2-bundle-vol may ignore some important files 2013-08-08 19:00:19 +01:00
Mark Peek 253d32c274 website: add myself to the community page 2013-08-08 10:03:09 -07:00
Mitchell Hashimoto 0b471adc7d Merge pull request #255 from johnbellone/patch-1
Update packer.go to cleanup plugin clients on bad CLI exit.
2013-08-08 09:31:36 -07:00
John Bellone 13ca445e13 Update packer.go to cleanup plugin clients on exit.
Since defer does not fire when exiting the application with os.Exit we need to be sure that we cleanup all of the clients on failures from the CLI and creating a new environment object.
2013-08-08 12:16:28 -04:00
Mark Peek 87e61b643c builder/amazon/all: address feedback for ami tag refactoring change
Use explicit name for Tag field and note tags added in the ui.
2013-08-07 19:37:41 -07:00
Mitchell Hashimoto 6a6aa146ac up version for dev 2013-08-07 15:47:43 -07:00
Jeremy Carroll a81fc2d837 Convert string to byte array 2013-08-07 15:20:49 -07:00
Jeremy Carroll 9eda3fa327 Testing adding user-data support to Packer 2013-08-07 14:33:20 -07:00
104 changed files with 2968 additions and 655 deletions
+41
View File
@@ -1,3 +1,44 @@
## 0.3.0 (August 12, 2013)
BACKWARDS INCOMPATIBILITIES:
* All `{{.CreateTime}}` variables within templates (such as for AMI names)
are now replaced with `{{timestamp}}`. Run `packer fix` to fix your
templates.
FEATURES:
* **User Variables** allow you to specify variables within your templates
that can be replaced using the command-line, files, or environmental
variables. This dramatically improves the portability of packer templates.
See the documentation for more information.
* **Machine-readable output** can be enabled by passing the
`-machine-readable` flag to _any_ Packer command.
* All strings in a template are now processed for variables/functions,
so things like `{{timestamp}}` can be used everywhere. More features will
be added in the future.
* The `amazon` builders (all of them) can now have attributes of their
resulting AMIs modified, such as access permissions and product codes.
IMPROVEMENTS:
* builder/amazon/all: User data can be passed to start the instances. [GH-253]
* provisioner/salt-masterless: `local_state_tree` is no longer required,
allowing you to use shell provisioner (or others) to bring this down.
[GH-269]
BUG FIXES:
* builder/amazon/ebs,instance: Retry deleing security group a few times.
[GH-278]
* builder/vmware: Workstation works on Windows XP now. [GH-238]
* builder/vmware: Look for files on Windows in multiple locations
using multiple environmental variables. [GH-263]
* provisioner/salt-masterless: states aren't deleted after the run
anymore. [GH-265]
* provisioner/salt-masterless: error if any commands exit with a non-zero
exit status. [GH-266]
## 0.2.3 (August 7, 2013)
IMPROVEMENTS:
+1 -1
View File
@@ -43,7 +43,7 @@ own.
"source_ami": "ami-de0d9eb7",
"instance_type": "t1.micro",
"ssh_username": "ubuntu",
"ami_name": "packer-example {{.CreateTime}}"
"ami_name": "packer-example {{timestamp}}"
}]
}
```
+52 -15
View File
@@ -14,7 +14,6 @@ import (
"github.com/mitchellh/packer/packer"
"log"
"runtime"
"text/template"
)
// The unique ID for this builder
@@ -25,8 +24,8 @@ const BuilderId = "mitchellh.amazon.chroot"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
awscommon.AccessConfig `mapstructure:",squash"`
awscommon.AMIConfig `mapstructure:",squash"`
AMIName string `mapstructure:"ami_name"`
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
CopyFiles []string `mapstructure:"copy_files"`
DevicePath string `mapstructure:"device_path"`
@@ -34,6 +33,8 @@ type Config struct {
MountPath string `mapstructure:"mount_path"`
SourceAmi string `mapstructure:"source_ami"`
UnmountCommand string `mapstructure:"unmount_command"`
tpl *common.Template
}
type Builder struct {
@@ -47,6 +48,12 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return err
}
b.config.tpl, err = common.NewTemplate()
if err != nil {
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
// Defaults
if b.config.ChrootMounts == nil {
b.config.ChrootMounts = make([][]string, 0)
@@ -84,31 +91,56 @@ func (b *Builder) Prepare(raws ...interface{}) error {
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.tpl)...)
if b.config.AMIName == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("ami_name must be specified"))
} else {
_, err = template.New("ami").Parse(b.config.AMIName)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing ami_name: %s", err))
}
}
for _, mounts := range b.config.ChrootMounts {
for i, mounts := range b.config.ChrootMounts {
if len(mounts) != 3 {
errs = packer.MultiErrorAppend(
errs, errors.New("Each chroot_mounts entry should be three elements."))
break
}
for j, entry := range mounts {
b.config.ChrootMounts[i][j], err = b.config.tpl.Process(entry, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing chroot_mounts[%d][%d]: %s",
i, j, err))
}
}
}
for i, file := range b.config.CopyFiles {
var err error
b.config.CopyFiles[i], err = b.config.tpl.Process(file, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing copy_files[%d]: %s",
i, err))
}
}
if b.config.SourceAmi == "" {
errs = packer.MultiErrorAppend(errs, errors.New("source_ami is required."))
}
templates := map[string]*string{
"device_path": &b.config.DevicePath,
"mount_command": &b.config.MountCommand,
"source_ami": &b.config.SourceAmi,
"unmount_command": &b.config.UnmountCommand,
}
for n, ptr := range templates {
var err error
*ptr, err = b.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
@@ -157,6 +189,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepEarlyCleanup{},
&StepSnapshot{},
&StepRegisterAMI{},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
},
}
// Run!
+8 -6
View File
@@ -9,7 +9,6 @@ import (
"os"
"os/exec"
"path/filepath"
"text/template"
)
type mountPathData struct {
@@ -30,14 +29,17 @@ func (s *StepMountDevice) Run(state map[string]interface{}) multistep.StepAction
ui := state["ui"].(packer.Ui)
device := state["device"].(string)
mountPathRaw := new(bytes.Buffer)
t := template.Must(template.New("mountPath").Parse(config.MountPath))
t.Execute(mountPathRaw, &mountPathData{
mountPath, err := config.tpl.Process(config.MountPath, &mountPathData{
Device: filepath.Base(device),
})
var err error
mountPath := mountPathRaw.String()
if err != nil {
err := fmt.Errorf("Error preparing mount directory: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
mountPath, err = filepath.Abs(mountPath)
if err != nil {
err := fmt.Errorf("Error preparing mount directory: %s", err)
+1 -19
View File
@@ -1,21 +1,13 @@
package chroot
import (
"bytes"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
"strconv"
"text/template"
"time"
)
type amiNameData struct {
CreateTime string
}
// StepRegisterAMI creates the AMI.
type StepRegisterAMI struct{}
@@ -26,16 +18,6 @@ func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction
snapshotId := state["snapshot_id"].(string)
ui := state["ui"].(packer.Ui)
// Parse the name of the AMI
amiNameBuf := new(bytes.Buffer)
tData := amiNameData{
strconv.FormatInt(time.Now().UTC().Unix(), 10),
}
t := template.Must(template.New("ami").Parse(config.AMIName))
t.Execute(amiNameBuf, tData)
amiName := amiNameBuf.String()
ui.Say("Registering the AMI...")
blockDevices := make([]ec2.BlockDeviceMapping, len(image.BlockDevices))
for i, device := range image.BlockDevices {
@@ -48,7 +30,7 @@ func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction
}
registerOpts := &ec2.RegisterImage{
Name: amiName,
Name: config.AMIName,
Architecture: image.Architecture,
KernelId: image.KernelId,
RamdiskId: image.RamdiskId,
+31 -2
View File
@@ -3,6 +3,7 @@ package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/packer/common"
"strings"
"unicode"
)
@@ -36,12 +37,40 @@ func (c *AccessConfig) Region() (aws.Region, error) {
return aws.Regions[region], nil
}
func (c *AccessConfig) Prepare() []error {
func (c *AccessConfig) Prepare(t *common.Template) []error {
if t == nil {
var err error
t, err = common.NewTemplate()
if err != nil {
return []error{err}
}
}
templates := map[string]*string{
"access_key": &c.AccessKey,
"secret_key": &c.SecretKey,
"region": &c.RawRegion,
}
errs := make([]error, 0)
for n, ptr := range templates {
var err error
*ptr, err = t.Process(*ptr, nil)
if err != nil {
errs = append(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
if c.RawRegion != "" {
if _, ok := aws.Regions[c.RawRegion]; !ok {
return []error{fmt.Errorf("Unknown region: %s", c.RawRegion)}
errs = append(errs, fmt.Errorf("Unknown region: %s", c.RawRegion))
}
}
if len(errs) > 0 {
return errs
}
return nil
}
+3 -3
View File
@@ -11,17 +11,17 @@ func testAccessConfig() *AccessConfig {
func TestAccessConfigPrepare_Region(t *testing.T) {
c := testAccessConfig()
c.RawRegion = ""
if err := c.Prepare(); err != nil {
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.RawRegion = "us-east-12"
if err := c.Prepare(); err == nil {
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
c.RawRegion = "us-east-1"
if err := c.Prepare(); err != nil {
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
}
+67
View File
@@ -0,0 +1,67 @@
package common
import (
"fmt"
"github.com/mitchellh/packer/common"
)
// AMIConfig is for common configuration related to creating AMIs.
type AMIConfig struct {
AMIName string `mapstructure:"ami_name"`
AMIDescription string `mapstructure:"ami_description"`
AMIUsers []string `mapstructure:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes"`
}
func (c *AMIConfig) Prepare(t *common.Template) []error {
if t == nil {
var err error
t, err = common.NewTemplate()
if err != nil {
return []error{err}
}
}
templates := map[string]*string{
"ami_name": &c.AMIName,
"ami_description": &c.AMIDescription,
}
errs := make([]error, 0)
for n, ptr := range templates {
var err error
*ptr, err = t.Process(*ptr, nil)
if err != nil {
errs = append(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
sliceTemplates := map[string][]string{
"ami_users": c.AMIUsers,
"ami_groups": c.AMIGroups,
"ami_product_codes": c.AMIProductCodes,
}
for n, slice := range sliceTemplates {
for i, elem := range slice {
var err error
slice[i], err = t.Process(elem, nil)
if err != nil {
errs = append(
errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err))
}
}
}
if c.AMIName == "" {
errs = append(errs, fmt.Errorf("ami_name must be specified"))
}
if len(errs) > 0 {
return errs
}
return nil
}
+23
View File
@@ -0,0 +1,23 @@
package common
import (
"testing"
)
func testAMIConfig() *AMIConfig {
return &AMIConfig{
AMIName: "foo",
}
}
func TestAMIConfigPrepare_Region(t *testing.T) {
c := testAMIConfig()
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AMIName = ""
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
}
+1
View File
@@ -36,6 +36,7 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.Instances([]string{i.InstanceId}, ec2.NewFilter())
if err != nil {
log.Printf("Error on InstanceStateRefresh: %s", err)
return nil, "", err
}
+41 -1
View File
@@ -3,6 +3,8 @@ package common
import (
"errors"
"fmt"
"github.com/mitchellh/packer/common"
"os"
"time"
)
@@ -12,6 +14,8 @@ 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"`
@@ -23,7 +27,15 @@ type RunConfig struct {
sshTimeout time.Duration
}
func (c *RunConfig) Prepare() []error {
func (c *RunConfig) Prepare(t *common.Template) []error {
if t == nil {
var err error
t, err = common.NewTemplate()
if err != nil {
return []error{err}
}
}
// Defaults
if c.SSHPort == 0 {
c.SSHPort = 22
@@ -48,6 +60,34 @@ func (c *RunConfig) Prepare() []error {
errs = append(errs, errors.New("An ssh_username must be specified"))
}
if c.UserData != "" && c.UserDataFile != "" {
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
} else if c.UserDataFile != "" {
if _, err := os.Stat(c.UserDataFile); err != nil {
errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
}
}
templates := map[string]*string{
"iam_instance_profile": &c.IamInstanceProfile,
"instance_type": &c.InstanceType,
"ssh_timeout": &c.RawSSHTimeout,
"security_group_id": &c.SecurityGroupId,
"ssh_username": &c.SSHUsername,
"source_ami": &c.SourceAmi,
"subnet_id": &c.SubnetId,
"vpc_id": &c.VpcId,
}
for n, ptr := range templates {
var err error
*ptr, err = t.Process(*ptr, nil)
if err != nil {
errs = append(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
+47 -8
View File
@@ -1,6 +1,7 @@
package common
import (
"io/ioutil"
"os"
"testing"
)
@@ -24,7 +25,7 @@ func testConfig() *RunConfig {
func TestRunConfigPrepare(t *testing.T) {
c := testConfig()
err := c.Prepare()
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
@@ -33,7 +34,7 @@ func TestRunConfigPrepare(t *testing.T) {
func TestRunConfigPrepare_InstanceType(t *testing.T) {
c := testConfig()
c.InstanceType = ""
if err := c.Prepare(); len(err) != 1 {
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
@@ -41,7 +42,7 @@ func TestRunConfigPrepare_InstanceType(t *testing.T) {
func TestRunConfigPrepare_SourceAmi(t *testing.T) {
c := testConfig()
c.SourceAmi = ""
if err := c.Prepare(); len(err) != 1 {
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
@@ -49,7 +50,7 @@ func TestRunConfigPrepare_SourceAmi(t *testing.T) {
func TestRunConfigPrepare_SSHPort(t *testing.T) {
c := testConfig()
c.SSHPort = 0
if err := c.Prepare(); len(err) != 0 {
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
@@ -58,7 +59,7 @@ func TestRunConfigPrepare_SSHPort(t *testing.T) {
}
c.SSHPort = 44
if err := c.Prepare(); len(err) != 0 {
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
@@ -70,12 +71,12 @@ func TestRunConfigPrepare_SSHPort(t *testing.T) {
func TestRunConfigPrepare_SSHTimeout(t *testing.T) {
c := testConfig()
c.RawSSHTimeout = ""
if err := c.Prepare(); len(err) != 0 {
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
c.RawSSHTimeout = "bad"
if err := c.Prepare(); len(err) != 1 {
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
@@ -83,7 +84,45 @@ func TestRunConfigPrepare_SSHTimeout(t *testing.T) {
func TestRunConfigPrepare_SSHUsername(t *testing.T) {
c := testConfig()
c.SSHUsername = ""
if err := c.Prepare(); len(err) != 1 {
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_UserData(t *testing.T) {
c := testConfig()
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer tf.Close()
c.UserData = "foo"
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_UserDataFile(t *testing.T) {
c := testConfig()
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
c.UserDataFile = "idontexistidontthink"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer tf.Close()
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
@@ -22,6 +22,7 @@ func (s *StepCreateTags) Run(state map[string]interface{}) multistep.StepAction
var ec2Tags []ec2.Tag
for key, value := range s.Tags {
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
ec2Tags = append(ec2Tags, ec2.Tag{key, value})
}
@@ -0,0 +1,55 @@
package common
import (
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepModifyAMIAttributes struct {
Users []string
Groups []string
ProductCodes []string
Description string
}
func (s *StepModifyAMIAttributes) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
amis := state["amis"].(map[string]string)
ami := amis[ec2conn.Region.Name]
// Determine if there is any work to do.
valid := false
valid = valid || s.Description != ""
valid = valid || (s.Users != nil && len(s.Users) > 0)
valid = valid || (s.Groups != nil && len(s.Groups) > 0)
valid = valid || (s.ProductCodes != nil && len(s.ProductCodes) > 0)
if !valid {
return multistep.ActionContinue
}
options := &ec2.ModifyImageAttribute{
Description: s.Description,
AddUsers: s.Users,
AddGroups: s.Groups,
ProductCodes: s.ProductCodes,
}
ui.Say("Modifying AMI attributes...")
_, err := ec2conn.ModifyImageAttribute(ami, options)
if err != nil {
err := fmt.Errorf("Error modify AMI attributes: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepModifyAMIAttributes) Cleanup(state map[string]interface{}) {
// No cleanup...
}
@@ -5,12 +5,15 @@ import (
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
)
type StepRunSourceInstance struct {
ExpectedRootDevice string
InstanceType string
UserData string
UserDataFile string
SourceAMI string
IamInstanceProfile string
SubnetId string
@@ -24,10 +27,22 @@ func (s *StepRunSourceInstance) Run(state map[string]interface{}) multistep.Step
securityGroupId := state["securityGroupId"].(string)
ui := state["ui"].(packer.Ui)
userData := s.UserData
if s.UserDataFile != "" {
contents, err := ioutil.ReadFile(s.UserDataFile)
if err != nil {
state["error"] = fmt.Errorf("Problem reading user data file: %s", err)
return multistep.ActionHalt
}
userData = string(contents)
}
runOpts := &ec2.RunInstances{
KeyName: keyName,
ImageId: s.SourceAMI,
InstanceType: s.InstanceType,
UserData: []byte(userData),
MinCount: 0,
MaxCount: 0,
SecurityGroups: []ec2.SecurityGroup{ec2.SecurityGroup{Id: securityGroupId}},
+13 -2
View File
@@ -8,6 +8,7 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
type StepSecurityGroup struct {
@@ -83,9 +84,19 @@ func (s *StepSecurityGroup) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
ui.Say("Deleting temporary security group...")
_, err := ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: s.createdGroupId})
if err != nil {
var err error
for i := 0; i < 5; i++ {
_, err = ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: s.createdGroupId})
if err == nil {
break
}
log.Printf("Error deleting security group: %s", err)
time.Sleep(5 * time.Second)
}
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up security group. Please delete the group manually: %s", s.createdGroupId))
}
+39 -17
View File
@@ -6,7 +6,6 @@
package ebs
import (
"errors"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
@@ -14,7 +13,6 @@ import (
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"log"
"text/template"
)
// The unique ID for this builder
@@ -23,13 +21,13 @@ const BuilderId = "mitchellh.amazonebs"
type config struct {
common.PackerConfig `mapstructure:",squash"`
awscommon.AccessConfig `mapstructure:",squash"`
awscommon.AMIConfig `mapstructure:",squash"`
awscommon.RunConfig `mapstructure:",squash"`
// Configuration of the resulting AMI
AMIName string `mapstructure:"ami_name"`
// Tags for the AMI
Tags map[string]string
tpl *common.Template
}
type Builder struct {
@@ -43,23 +41,40 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return err
}
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare()...)
b.config.tpl, err = common.NewTemplate()
if err != nil {
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
// Accumulate any errors
if b.config.AMIName == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("ami_name must be specified"))
} else {
_, err = template.New("ami").Parse(b.config.AMIName)
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
// Accumulate any errors
newTags := make(map[string]string)
for k, v := range b.config.Tags {
k, err = b.config.tpl.Process(k, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing ami_name: %s", err))
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing tag key %s: %s", k, err))
continue
}
v, err = b.config.tpl.Process(v, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing tag value '%s': %s", v, err))
continue
}
newTags[k] = v
}
b.config.Tags = newTags
if errs != nil && len(errs.Errors) > 0 {
return errs
}
@@ -99,6 +114,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&awscommon.StepRunSourceInstance{
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,
@@ -111,7 +128,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&common.StepProvision{},
&stepStopInstance{},
&stepCreateAMI{},
&awscommon.StepCreateTags{b.config.Tags},
&awscommon.StepCreateTags{Tags: b.config.Tags},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
},
}
// Run!
+2 -20
View File
@@ -1,44 +1,26 @@
package ebs
import (
"bytes"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
"strconv"
"text/template"
"time"
)
type stepCreateAMI struct{}
type amiNameData struct {
CreateTime string
}
func (s *stepCreateAMI) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(config)
ec2conn := state["ec2"].(*ec2.EC2)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
// Parse the name of the AMI
amiNameBuf := new(bytes.Buffer)
tData := amiNameData{
strconv.FormatInt(time.Now().UTC().Unix(), 10),
}
t := template.Must(template.New("ami").Parse(config.AMIName))
t.Execute(amiNameBuf, tData)
amiName := amiNameBuf.String()
// Create the image
ui.Say(fmt.Sprintf("Creating the AMI: %s", amiName))
ui.Say(fmt.Sprintf("Creating the AMI: %s", config.AMIName))
createOpts := &ec2.CreateImage{
InstanceId: instance.InstanceId,
Name: amiName,
Name: config.AMIName,
}
createResp, err := ec2conn.CreateImage(createOpts)
+54 -17
View File
@@ -13,7 +13,6 @@ import (
"log"
"os"
"strings"
"text/template"
)
// The unique ID for this builder
@@ -24,10 +23,10 @@ const BuilderId = "mitchellh.amazon.instance"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
awscommon.AccessConfig `mapstructure:",squash"`
awscommon.AMIConfig `mapstructure:",squash"`
awscommon.RunConfig `mapstructure:",squash"`
AccountId string `mapstructure:"account_id"`
AMIName string `mapstructure:"ami_name"`
BundleDestination string `mapstructure:"bundle_destination"`
BundlePrefix string `mapstructure:"bundle_prefix"`
BundleUploadCommand string `mapstructure:"bundle_upload_command"`
@@ -37,6 +36,8 @@ type Config struct {
X509CertPath string `mapstructure:"x509_cert_path"`
X509KeyPath string `mapstructure:"x509_key_path"`
X509UploadPath string `mapstructure:"x509_upload_path"`
tpl *common.Template
}
type Builder struct {
@@ -50,12 +51,18 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return err
}
b.config.tpl, err = common.NewTemplate()
if err != nil {
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
if b.config.BundleDestination == "" {
b.config.BundleDestination = "/tmp"
}
if b.config.BundlePrefix == "" {
b.config.BundlePrefix = "image-{{.CreateTime}}"
b.config.BundlePrefix = "image-{{timestamp}}"
}
if b.config.BundleUploadCommand == "" {
@@ -87,8 +94,41 @@ func (b *Builder) Prepare(raws ...interface{}) error {
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
validates := map[string]*string{
"bundle_upload_command": &b.config.BundleUploadCommand,
"bundle_vol_command": &b.config.BundleVolCommand,
}
for n, ptr := range validates {
if err := b.config.tpl.Validate(*ptr); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error parsing %s: %s", n, err))
}
}
templates := map[string]*string{
"account_id": &b.config.AccountId,
"ami_name": &b.config.AMIName,
"bundle_destination": &b.config.BundleDestination,
"bundle_prefix": &b.config.BundlePrefix,
"s3_bucket": &b.config.S3Bucket,
"x509_cert_path": &b.config.X509CertPath,
"x509_key_path": &b.config.X509KeyPath,
"x509_upload_path": &b.config.X509UploadPath,
}
for n, ptr := range templates {
var err error
*ptr, err = b.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
if b.config.AccountId == "" {
errs = packer.MultiErrorAppend(errs, errors.New("account_id is required"))
@@ -96,17 +136,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.AccountId = strings.Replace(b.config.AccountId, "-", "", -1)
}
if b.config.AMIName == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("ami_name must be specified"))
} else {
_, err = template.New("ami").Parse(b.config.AMIName)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing ami_name: %s", err))
}
}
if b.config.S3Bucket == "" {
errs = packer.MultiErrorAppend(errs, errors.New("s3_bucket is required"))
}
@@ -164,6 +193,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&awscommon.StepRunSourceInstance{
ExpectedRootDevice: "instance-store",
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
SubnetId: b.config.SubnetId,
},
@@ -177,7 +208,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepBundleVolume{},
&StepUploadBundle{},
&StepRegisterAMI{},
&awscommon.StepCreateTags{b.config.Tags},
&awscommon.StepCreateTags{Tags: b.config.Tags},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
},
}
// Run!
+1 -1
View File
@@ -115,7 +115,7 @@ func TestBuilderPrepare_BundlePrefix(t *testing.T) {
t.Fatalf("err: %s", err)
}
if b.config.BundlePrefix != "image-{{.CreateTime}}" {
if b.config.BundlePrefix == "" {
t.Fatalf("bad: %s", b.config.BundlePrefix)
}
}
+11 -22
View File
@@ -1,14 +1,10 @@
package instance
import (
"bytes"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"strconv"
"text/template"
"time"
)
type bundleCmdData struct {
@@ -21,10 +17,6 @@ type bundleCmdData struct {
PrivatePath string
}
type bundlePrefixData struct {
CreateTime string
}
type StepBundleVolume struct{}
func (s *StepBundleVolume) Run(state map[string]interface{}) multistep.StepAction {
@@ -55,29 +47,26 @@ func (s *StepBundleVolume) Run(state map[string]interface{}) multistep.StepActio
}
// Bundle the volume
var bundlePrefix bytes.Buffer
prefixTData := bundlePrefixData{
CreateTime: strconv.FormatInt(time.Now().UTC().Unix(), 10),
}
t := template.Must(template.New("bundlePrefix").Parse(config.BundlePrefix))
t.Execute(&bundlePrefix, prefixTData)
var bundleCmd bytes.Buffer
tData := bundleCmdData{
var err error
config.BundleVolCommand, err = config.tpl.Process(config.BundleVolCommand, bundleCmdData{
AccountId: config.AccountId,
Architecture: instance.Architecture,
CertPath: x509RemoteCertPath,
Destination: config.BundleDestination,
KeyPath: x509RemoteKeyPath,
Prefix: bundlePrefix.String(),
Prefix: config.BundlePrefix,
PrivatePath: config.X509UploadPath,
})
if err != nil {
err := fmt.Errorf("Error processing bundle volume command: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
t = template.Must(template.New("bundleCmd").Parse(config.BundleVolCommand))
t.Execute(&bundleCmd, tData)
ui.Say("Bundling the volume...")
cmd = new(packer.RemoteCmd)
cmd.Command = bundleCmd.String()
cmd.Command = config.BundleVolCommand
if err := cmd.StartWithUi(comm, ui); err != nil {
state["error"] = fmt.Errorf("Error bundling volume: %s", err)
ui.Error(state["error"].(error).Error())
@@ -93,7 +82,7 @@ func (s *StepBundleVolume) Run(state map[string]interface{}) multistep.StepActio
}
// Store the manifest path
manifestName := bundlePrefix.String() + ".manifest.xml"
manifestName := config.BundlePrefix + ".manifest.xml"
state["manifest_name"] = manifestName
state["manifest_path"] = fmt.Sprintf(
"%s/%s", config.BundleDestination, manifestName)
+1 -19
View File
@@ -1,21 +1,13 @@
package instance
import (
"bytes"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
"strconv"
"text/template"
"time"
)
type amiNameData struct {
CreateTime string
}
type StepRegisterAMI struct{}
func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction {
@@ -24,20 +16,10 @@ func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction
manifestPath := state["remote_manifest_path"].(string)
ui := state["ui"].(packer.Ui)
// Parse the name of the AMI
amiNameBuf := new(bytes.Buffer)
tData := amiNameData{
strconv.FormatInt(time.Now().UTC().Unix(), 10),
}
t := template.Must(template.New("ami").Parse(config.AMIName))
t.Execute(amiNameBuf, tData)
amiName := amiNameBuf.String()
ui.Say("Registering the AMI...")
registerOpts := &ec2.RegisterImage{
ImageLocation: manifestPath,
Name: amiName,
Name: config.AMIName,
}
registerResp, err := ec2conn.RegisterImage(registerOpts)
@@ -1,11 +1,9 @@
package instance
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"text/template"
)
type uploadCmdData struct {
@@ -25,19 +23,23 @@ func (s *StepUploadBundle) Run(state map[string]interface{}) multistep.StepActio
manifestPath := state["manifest_path"].(string)
ui := state["ui"].(packer.Ui)
var uploadCmd bytes.Buffer
tData := uploadCmdData{
var err error
config.BundleUploadCommand, err = config.tpl.Process(config.BundleUploadCommand, uploadCmdData{
AccessKey: config.AccessKey,
BucketName: config.S3Bucket,
BundleDirectory: config.BundleDestination,
ManifestPath: manifestPath,
SecretKey: config.SecretKey,
})
if err != nil {
err := fmt.Errorf("Error processing bundle upload command: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
t := template.Must(template.New("uploadCmd").Parse(config.BundleUploadCommand))
t.Execute(&uploadCmd, tData)
ui.Say("Uploading the bundle...")
cmd := &packer.RemoteCmd{Command: uploadCmd.String()}
cmd := &packer.RemoteCmd{Command: config.BundleUploadCommand}
if err := cmd.StartWithUi(comm, ui); err != nil {
state["error"] = fmt.Errorf("Error uploading volume: %s", err)
ui.Error(state["error"].(error).Error())
+33 -28
View File
@@ -4,7 +4,6 @@
package digitalocean
import (
"bytes"
"errors"
"fmt"
"github.com/mitchellh/multistep"
@@ -12,18 +11,12 @@ import (
"github.com/mitchellh/packer/packer"
"log"
"os"
"strconv"
"text/template"
"time"
)
// The unique id for the builder
const BuilderId = "pearkes.digitalocean"
type snapshotNameData struct {
CreateTime string
}
// Configuration tells the builder the credentials
// to use while communicating with DO and describes the image
// you are creating
@@ -36,11 +29,10 @@ type config struct {
SizeID uint `mapstructure:"size_id"`
ImageID uint `mapstructure:"image_id"`
SnapshotName string
SnapshotName string `mapstructure:"snapshot_name"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort uint `mapstructure:"ssh_port"`
RawSnapshotName string `mapstructure:"snapshot_name"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
RawEventDelay string `mapstructure:"event_delay"`
RawStateTimeout string `mapstructure:"state_timeout"`
@@ -50,6 +42,8 @@ type config struct {
sshTimeout time.Duration
eventDelay time.Duration
stateTimeout time.Duration
tpl *common.Template
}
type Builder struct {
@@ -63,6 +57,12 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return err
}
b.config.tpl, err = common.NewTemplate()
if err != nil {
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
@@ -92,6 +92,11 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.ImageID = 284203
}
if b.config.SnapshotName == "" {
// Default to packer-{{ unix timestamp (utc) }}
b.config.SnapshotName = "packer-{{timestamp}}"
}
if b.config.SSHUsername == "" {
// Default to "root". You can override this if your
// SourceImage has a different user account then the DO default
@@ -103,11 +108,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.SSHPort = 22
}
if b.config.RawSnapshotName == "" {
// Default to packer-{{ unix timestamp (utc) }}
b.config.RawSnapshotName = "packer-{{.CreateTime}}"
}
if b.config.RawSSHTimeout == "" {
// Default to 1 minute timeouts
b.config.RawSSHTimeout = "1m"
@@ -125,6 +125,25 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.RawStateTimeout = "6m"
}
templates := map[string]*string{
"client_id": &b.config.ClientID,
"api_key": &b.config.APIKey,
"snapshot_name": &b.config.SnapshotName,
"ssh_username": &b.config.SSHUsername,
"ssh_timeout": &b.config.RawSSHTimeout,
"event_delay": &b.config.RawEventDelay,
"state_timeout": &b.config.RawStateTimeout,
}
for n, ptr := range templates {
var err error
*ptr, err = b.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
// Required configurations that will display errors if not set
if b.config.ClientID == "" {
errs = packer.MultiErrorAppend(
@@ -157,20 +176,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
b.config.stateTimeout = stateTimeout
// Parse the name of the snapshot
snapNameBuf := new(bytes.Buffer)
tData := snapshotNameData{
strconv.FormatInt(time.Now().UTC().Unix(), 10),
}
t, err := template.New("snapshot").Parse(b.config.RawSnapshotName)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing snapshot_name: %s", err))
} else {
t.Execute(snapNameBuf, tData)
b.config.SnapshotName = snapNameBuf.String()
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
+3 -3
View File
@@ -332,8 +332,8 @@ func TestBuilderPrepare_SnapshotName(t *testing.T) {
t.Fatalf("should not have error: %s", err)
}
if b.config.RawSnapshotName != "packer-{{.CreateTime}}" {
t.Errorf("invalid: %d", b.config.RawSnapshotName)
if b.config.SnapshotName == "" {
t.Errorf("invalid: %s", b.config.SnapshotName)
}
// Test set
@@ -345,7 +345,7 @@ func TestBuilderPrepare_SnapshotName(t *testing.T) {
}
// Test set with template
config["snapshot_name"] = "{{.CreateTime}}"
config["snapshot_name"] = "{{timestamp}}"
b = Builder{}
err = b.Prepare(config)
if err != nil {
+63
View File
@@ -57,6 +57,7 @@ type config struct {
bootWait time.Duration ``
shutdownTimeout time.Duration ``
sshWaitTimeout time.Duration ``
tpl *common.Template
}
func (b *Builder) Prepare(raws ...interface{}) error {
@@ -65,6 +66,12 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return err
}
b.config.tpl, err = common.NewTemplate()
if err != nil {
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
@@ -124,6 +131,53 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
}
// Errors
templates := map[string]*string{
"guest_additions_path": &b.config.GuestAdditionsPath,
"guest_additions_url": &b.config.GuestAdditionsURL,
"guest_additions_sha256": &b.config.GuestAdditionsSHA256,
"guest_os_type": &b.config.GuestOSType,
"http_directory": &b.config.HTTPDir,
"iso_checksum": &b.config.ISOChecksum,
"iso_checksum_type": &b.config.ISOChecksumType,
"iso_url": &b.config.ISOUrl,
"output_directory": &b.config.OutputDir,
"shutdown_command": &b.config.ShutdownCommand,
"ssh_password": &b.config.SSHPassword,
"ssh_username": &b.config.SSHUser,
"virtualbox_version_file": &b.config.VBoxVersionFile,
"vm_name": &b.config.VMName,
"boot_wait": &b.config.RawBootWait,
"shutdown_timeout": &b.config.RawShutdownTimeout,
"ssh_wait_timeout": &b.config.RawSSHWaitTimeout,
}
for n, ptr := range templates {
var err error
*ptr, err = b.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
for i, command := range b.config.BootCommand {
if err := b.config.tpl.Validate(command); err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
}
}
for i, file := range b.config.FloppyFiles {
var err error
b.config.FloppyFiles[i], err = b.config.tpl.Process(file, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing floppy_files[%d]: %s",
i, err))
}
}
if b.config.HTTPPortMin > b.config.HTTPPortMax {
errs = packer.MultiErrorAppend(
errs, errors.New("http_port_min must be less than http_port_max"))
@@ -215,6 +269,15 @@ func (b *Builder) Prepare(raws ...interface{}) error {
errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
}
for i, args := range b.config.VBoxManage {
for j, arg := range args {
if err := b.config.tpl.Validate(arg); err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing vboxmanage[%d][%d]: %s", i, j, err))
}
}
}
b.driver, err = b.newDriver()
if err != nil {
errs = packer.MultiErrorAppend(
+8 -6
View File
@@ -1,13 +1,11 @@
package virtualbox
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"strings"
"text/template"
"time"
"unicode"
"unicode/utf8"
@@ -49,11 +47,15 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
ui.Say("Typing the boot command...")
for _, command := range config.BootCommand {
var buf bytes.Buffer
t := template.Must(template.New("boot").Parse(command))
t.Execute(&buf, tplData)
command, err := config.tpl.Process(command, tplData)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
for _, code := range scancodes(buf.String()) {
for _, code := range scancodes(command) {
if code == "wait" {
time.Sleep(1 * time.Second)
continue
@@ -1,12 +1,10 @@
package virtualbox
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
"text/template"
)
type guestAdditionsPathTemplate struct {
@@ -39,12 +37,16 @@ func (s *stepUploadGuestAdditions) Run(state map[string]interface{}) multistep.S
Version: version,
}
var processedPath bytes.Buffer
t := template.Must(template.New("path").Parse(config.GuestAdditionsPath))
t.Execute(&processedPath, tplData)
config.GuestAdditionsPath, err = config.tpl.Process(config.GuestAdditionsPath, tplData)
if err != nil {
err := fmt.Errorf("Error preparing guest additions path: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Uploading VirtualBox guest additions ISO...")
if err := comm.Upload(processedPath.String(), f); err != nil {
if err := comm.Upload(config.GuestAdditionsPath, f); err != nil {
state["error"] = fmt.Errorf("Error uploading guest additions: %s", err)
return multistep.ActionHalt
}
+8 -6
View File
@@ -1,12 +1,10 @@
package virtualbox
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"strings"
"text/template"
)
type commandTemplate struct {
@@ -40,10 +38,14 @@ func (s *stepVBoxManage) Run(state map[string]interface{}) multistep.StepAction
copy(command, originalCommand)
for i, arg := range command {
var buf bytes.Buffer
t := template.Must(template.New("arg").Parse(arg))
t.Execute(&buf, tplData)
command[i] = buf.String()
var err error
command[i], err = config.tpl.Process(arg, tplData)
if err != nil {
err := fmt.Errorf("Error preparing vboxmanage command: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
}
ui.Message(fmt.Sprintf("Executing: %s", strings.Join(command, " ")))
+73
View File
@@ -58,6 +58,7 @@ type config struct {
bootWait time.Duration ``
shutdownTimeout time.Duration ``
sshWaitTimeout time.Duration ``
tpl *common.Template
}
func (b *Builder) Prepare(raws ...interface{}) error {
@@ -66,6 +67,12 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return err
}
b.config.tpl, err = common.NewTemplate()
if err != nil {
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
@@ -121,6 +128,72 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.ToolsUploadPath = "{{ .Flavor }}.iso"
}
// Errors
templates := map[string]*string{
"disk_name": &b.config.DiskName,
"guest_os_type": &b.config.GuestOSType,
"http_directory": &b.config.HTTPDir,
"iso_checksum": &b.config.ISOChecksum,
"iso_checksum_type": &b.config.ISOChecksumType,
"iso_url": &b.config.ISOUrl,
"output_directory": &b.config.OutputDir,
"shutdown_command": &b.config.ShutdownCommand,
"ssh_password": &b.config.SSHPassword,
"ssh_username": &b.config.SSHUser,
"tools_upload_flavor": &b.config.ToolsUploadFlavor,
"vm_name": &b.config.VMName,
"boot_wait": &b.config.RawBootWait,
"shutdown_timeout": &b.config.RawShutdownTimeout,
"ssh_wait_timeout": &b.config.RawSSHWaitTimeout,
}
for n, ptr := range templates {
var err error
*ptr, err = b.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
for i, command := range b.config.BootCommand {
if err := b.config.tpl.Validate(command); err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
}
}
for i, file := range b.config.FloppyFiles {
var err error
b.config.FloppyFiles[i], err = b.config.tpl.Process(file, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing floppy_files[%d]: %s",
i, err))
}
}
newVMXData := make(map[string]string)
for k, v := range b.config.VMXData {
k, err = b.config.tpl.Process(k, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing VMX data key %s: %s", k, err))
continue
}
v, err = b.config.tpl.Process(v, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing VMX data value '%s': %s", v, err))
continue
}
newVMXData[k] = v
}
b.config.VMXData = newVMXData
if b.config.HTTPPortMin > b.config.HTTPPortMax {
errs = packer.MultiErrorAppend(
errs, errors.New("http_port_min must be less than http_port_max"))
+4
View File
@@ -135,3 +135,7 @@ func (d *Workstation9Driver) ToolsIsoPath(flavor string) string {
func (d *Workstation9Driver) DhcpLeasesPath(device string) string {
return workstationDhcpLeasesPath(device)
}
func (d *Workstation9Driver) VmnetnatConfPath() string {
return workstationVmnetnatConfPath()
}
@@ -41,3 +41,7 @@ func workstationDhcpLeasesPath(device string) string {
func workstationToolsIsoPath(flavor string) string {
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
}
func workstationVmnetnatConfPath() string {
return ""
}
+117 -32
View File
@@ -23,58 +23,44 @@ func workstationFindVdiskManager() (string, error) {
return path, nil
}
path, err = workstationVMwareRoot()
if err != nil {
return "", err
}
return filepath.Join(path, "vmware-vdiskmanager.exe"), nil
return findFile("vmware-vdiskmanager.exe", workstationProgramFilePaths()), nil
}
func workstationFindVMware() (string, error) {
path, err := exec.LookPath("vmware.exe")
path, _ := exec.LookPath("vmware.exe")
if err == nil {
return path, nil
}
path, err = workstationVMwareRoot()
if err != nil {
return "", err
}
return filepath.Join(path, "vmware.exe"), nil
return findFile("vmware.exe", workstationProgramFilePaths()), nil
}
func workstationFindVmrun() (string, error) {
path, err := exec.LookPath("vmrun.exe")
path, _ := exec.LookPath("vmrun.exe")
if err == nil {
return path, nil
}
path, err = workstationVMwareRoot()
if err != nil {
return "", err
}
return filepath.Join(path, "vmrun.exe"), nil
return findFile("vmrun.exe", workstationProgramFilePaths()), nil
}
func workstationToolsIsoPath(flavor string) string {
path, err := workstationVMwareRoot()
if err != nil {
return ""
}
return filepath.Join(path, flavor+".iso")
return findFile(flavor+".iso", workstationProgramFilePaths()), nil
}
func workstationDhcpLeasesPath(device string) string {
programData := os.Getenv("ProgramData")
if programData == "" {
return ""
path, err := workstationDhcpLeasesPathRegistry()
if err != nil {
log.Printf("Error finding leases in registry: %s", err)
} else if _, err := os.Stat(path); err == nil {
return path
}
return filepath.Join(programData, "/VMware/vmnetdhcp.leases")
return findFile("vmnetdhcp.leases", workstationDataFilePaths()), nil
}
func workstationVmnetnatConfPath() string {
return findFile("vmnetnat.conf", workstationDataFilePaths()), nil
}
// See http://blog.natefinch.com/2012/11/go-win-stuff.html
@@ -126,6 +112,105 @@ func workstationVMwareRoot() (s string, err error) {
return
}
s = strings.Replace(s, "\\", "/", -1)
return
return normalizePath(s), nil
}
// This reads the VMware DHCP leases path from the Windows registry.
func workstationDhcpLeasesPathRegistry() (s string, err error) {
key := "SYSTEM\\CurrentControlSet\\services\\VMnetDHCP\\Parameters"
subkey := "LeaseFile"
s, err = readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey)
if err != nil {
log.Printf(`Unable to read registry key %s\%s`, key, subkey)
return
}
return normalizePath(s), nil
}
func normalizePath(path string) string {
path = strings.Replace(path, "\\", "/", -1)
path = strings.Replace(path, "//", "/", -1)
path = strings.TrimRight(path, "/")
return path
}
func findFile(file string, paths []string) string {
for _, path := range paths {
path = filepath.Join(path, file)
path = normalizePath(path)
log.Printf("Searching for file '%s'", path)
if _, err := os.Stat(path); err != nil {
log.Printf("Found file '%s'", path)
return path
}
}
log.Printf("File not found: '%s'", file)
return ""
}
// workstationProgramFilesPaths returns a list of paths that are eligible
// to contain program files we may want just as vmware.exe.
func workstationProgramFilePaths() []string {
path, err := workstationVMwareRoot()
if err != nil {
log.Printf("Error finding VMware root: %s", err)
}
paths := make([]string, 0, 5)
if os.Getenv("VMWARE_HOME") != "" {
paths = append(paths, os.Getenv("VMWARE_HOME"))
}
if path != "" {
paths = append(paths, path)
}
if os.Getenv("ProgramFiles(x86)") != "" {
paths = append(paths,
filepath.Join(os.Getenv("ProgramFiles(x86)"), "/VMware/VMware Workstation"))
}
if os.Getenv("ProgramFiles") != "" {
paths = append(paths,
filepath.Join(os.Getenv("ProgramFiles"), "/VMware/VMware Workstation"))
}
return paths
}
// workstationDataFilePaths returns a list of paths that are eligible
// to contain data files we may want such as vmnet NAT configuration files.
func workstationDataFilePaths() []string {
leasesPath, err := workstationVmnetDhcpLeasesPathFromRegistry()
if err != nil {
log.Printf("Error getting DHCP leases path: %s", err)
}
if leasesPath != "" {
leasesPath = filepath.Dir(leasesPath)
}
paths := make([]string, 0, 5)
if os.Getenv("VMWARE_DATA") != "" {
paths = append(paths, os.Getenv("VMWARE_DATA"))
}
if path != "" {
paths = append(paths, leasesPath)
}
if os.Getenv("ProgramData") != "" {
paths = append(paths,
filepath.Join(os.Getenv("ProgramData"), "/VMware"))
}
if os.Getenv("ALLUSERSPROFILE") != "" {
paths = append(paths,
filepath.Join(os.Getenv("ALLUSERSPROFILE"), "/Application Data/VMware"))
}
return paths
}
+7 -8
View File
@@ -6,7 +6,6 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
)
@@ -17,15 +16,15 @@ import (
type VMnetNatConfIPFinder struct{}
func (*VMnetNatConfIPFinder) HostIP() (string, error) {
programData := os.Getenv("ProgramData")
if programData == "" {
return "", errors.New("ProgramData directory not found.")
driver := &Workstation9Driver{}
vmnetnat := driver.VmnetnatConfPath()
if vmnetnat == "" {
return "", errors.New("Could not find NAT vmnet conf file")
}
programData = strings.Replace(programData, "\\", "/", -1)
vmnetnat := filepath.Join(programData, "/VMware/vmnetnat.conf")
if _, err := os.Stat(vmnetnat); err != nil {
return "", fmt.Errorf("Error with vmnetnat.conf: %s", err)
return "", fmt.Errorf("Could not find NAT vmnet conf file: %s", vmnetnat)
}
f, err := os.Open(vmnetnat)
@@ -62,5 +61,5 @@ func (*VMnetNatConfIPFinder) HostIP() (string, error) {
}
}
return "", errors.New("host IP not found in NAT config")
return "", errors.New("host IP not found in " + vmnetnat)
}
+8 -6
View File
@@ -1,7 +1,6 @@
package vmware
import (
"bytes"
"fmt"
"github.com/mitchellh/go-vnc"
"github.com/mitchellh/multistep"
@@ -10,7 +9,6 @@ import (
"net"
"runtime"
"strings"
"text/template"
"time"
"unicode"
"unicode/utf8"
@@ -90,11 +88,15 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
ui.Say("Typing the boot command over VNC...")
for _, command := range config.BootCommand {
var buf bytes.Buffer
t := template.Must(template.New("boot").Parse(command))
t.Execute(&buf, tplData)
command, err := config.tpl.Process(command, tplData)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
vncSendString(c, buf.String())
vncSendString(c, command)
}
return multistep.ActionContinue
+8 -6
View File
@@ -1,12 +1,10 @@
package vmware
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
"text/template"
)
type toolsUploadPathTemplate struct {
@@ -34,11 +32,15 @@ func (*stepUploadTools) Run(state map[string]interface{}) multistep.StepAction {
defer f.Close()
tplData := &toolsUploadPathTemplate{Flavor: config.ToolsUploadFlavor}
var processedPath bytes.Buffer
t := template.Must(template.New("path").Parse(config.ToolsUploadPath))
t.Execute(&processedPath, tplData)
config.ToolsUploadPath, err = config.tpl.Process(config.ToolsUploadPath, tplData)
if err != nil {
err := fmt.Errorf("Error preparing upload path: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := comm.Upload(processedPath.String(), f); err != nil {
if err := comm.Upload(config.ToolsUploadPath, f); err != nil {
state["error"] = fmt.Errorf("Error uploading VMware Tools: %s", err)
return multistep.ActionHalt
}
+49 -59
View File
@@ -4,11 +4,12 @@ import (
"bytes"
"flag"
"fmt"
cmdcommon "github.com/mitchellh/packer/common/command"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
"os"
"os/signal"
"strconv"
"strings"
"sync"
)
@@ -22,15 +23,13 @@ func (Command) Help() string {
func (c Command) Run(env packer.Environment, args []string) int {
var cfgDebug bool
var cfgForce bool
var cfgExcept []string
var cfgOnly []string
buildOptions := new(cmdcommon.BuildOptions)
cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError)
cmdFlags.Usage = func() { env.Ui().Say(c.Help()) }
cmdFlags.BoolVar(&cfgDebug, "debug", false, "debug mode for builds")
cmdFlags.BoolVar(&cfgForce, "force", false, "force a build if artifacts exist")
cmdFlags.Var((*stringSliceValue)(&cfgExcept), "except", "build all builds except these")
cmdFlags.Var((*stringSliceValue)(&cfgOnly), "only", "only build the given builds by name")
cmdcommon.BuildOptionFlags(cmdFlags, buildOptions)
if err := cmdFlags.Parse(args); err != nil {
return 1
}
@@ -41,23 +40,24 @@ func (c Command) Run(env packer.Environment, args []string) int {
return 1
}
if len(cfgOnly) > 0 && len(cfgExcept) > 0 {
env.Ui().Error("Only one of '-except' or '-only' may be specified.\n")
if err := buildOptions.Validate(); err != nil {
env.Ui().Error(err.Error())
env.Ui().Error("")
env.Ui().Error(c.Help())
return 1
}
userVars, err := buildOptions.AllUserVars()
if err != nil {
env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err))
env.Ui().Error("")
env.Ui().Error(c.Help())
return 1
}
// Read the file into a byte array so that we can parse the template
log.Printf("Reading template: %s", args[0])
tplData, err := ioutil.ReadFile(args[0])
if err != nil {
env.Ui().Error(fmt.Sprintf("Failed to read template file: %s", err))
return 1
}
// Parse the template into a machine-usable format
log.Println("Parsing template...")
tpl, err := packer.ParseTemplate(tplData)
tpl, err := packer.ParseTemplateFile(args[0])
if err != nil {
env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err))
return 1
@@ -72,47 +72,10 @@ func (c Command) Run(env packer.Environment, args []string) int {
}
// Go through each builder and compile the builds that we care about
buildNames := tpl.BuildNames()
builds := make([]packer.Build, 0, len(buildNames))
for _, buildName := range buildNames {
if len(cfgExcept) > 0 {
found := false
for _, only := range cfgExcept {
if buildName == only {
found = true
break
}
}
if found {
log.Printf("Skipping build '%s' because specified by -except.", buildName)
continue
}
}
if len(cfgOnly) > 0 {
found := false
for _, only := range cfgOnly {
if buildName == only {
found = true
break
}
}
if !found {
log.Printf("Skipping build '%s' because not specified by -only.", buildName)
continue
}
}
log.Printf("Creating build: %s", buildName)
build, err := tpl.Build(buildName, components)
if err != nil {
env.Ui().Error(fmt.Sprintf("Failed to create build '%s': \n\n%s", buildName, err))
return 1
}
builds = append(builds, build)
builds, err := buildOptions.Builds(tpl, components)
if err != nil {
env.Ui().Error(err.Error())
return 1
}
if cfgDebug {
@@ -150,7 +113,7 @@ func (c Command) Run(env packer.Environment, args []string) int {
log.Printf("Preparing build: %s", b.Name())
b.SetDebug(cfgDebug)
b.SetForce(cfgForce)
err := b.Prepare()
err := b.Prepare(userVars)
if err != nil {
env.Ui().Error(err.Error())
return 1
@@ -233,7 +196,16 @@ func (c Command) Run(env packer.Environment, args []string) int {
if len(artifacts) > 0 {
env.Ui().Say("\n==> Builds finished. The artifacts of successful builds are:")
for name, buildArtifacts := range artifacts {
for _, artifact := range buildArtifacts {
// Create a UI for the machine readable stuff to be targetted
ui := &packer.TargettedUi{
Target: name,
Ui: env.Ui(),
}
// Machine-readable helpful
ui.Machine("artifact-count", strconv.FormatInt(int64(len(buildArtifacts)), 10))
for i, artifact := range buildArtifacts {
var message bytes.Buffer
fmt.Fprintf(&message, "--> %s: ", name)
@@ -243,6 +215,24 @@ func (c Command) Run(env packer.Environment, args []string) int {
fmt.Fprint(&message, "<nothing>")
}
iStr := strconv.FormatInt(int64(i), 10)
if artifact != nil {
ui.Machine("artifact", iStr, "builder-id", artifact.BuilderId())
ui.Machine("artifact", iStr, "id", artifact.Id())
ui.Machine("artifact", iStr, "string", artifact.String())
files := artifact.Files()
ui.Machine("artifact",
iStr,
"files-count", strconv.FormatInt(int64(len(files)), 10))
for fi, file := range files {
fiStr := strconv.FormatInt(int64(fi), 10)
ui.Machine("artifact", iStr, "file", fiStr, file)
}
} else {
ui.Machine("artifact", iStr, "nil")
}
env.Ui().Say(message.String())
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
func testEnvironment() packer.Environment {
config := packer.DefaultEnvironmentConfig()
config.Ui = &packer.ReaderWriterUi{
config.Ui = &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
}
+3
View File
@@ -10,6 +10,9 @@ Options:
-debug Debug mode enabled for builds
-force Force a build to continue if artifacts exist, deletes existing artifacts
-machine-readable Machine-readable output
-except=foo,bar,baz Build all builds other than these
-only=foo,bar,baz Only build the given builds by name
-var 'key=value' Variable for templates, can be used multiple times.
-var-file=path JSON file containing user variables.
`
-14
View File
@@ -1,14 +0,0 @@
package build
import "strings"
type stringSliceValue []string
func (s *stringSliceValue) String() string {
return strings.Join(*s, ",")
}
func (s *stringSliceValue) Set(value string) error {
*s = strings.Split(value, ",")
return nil
}
+13 -2
View File
@@ -6,6 +6,7 @@ import (
"flag"
"fmt"
"github.com/mitchellh/packer/packer"
"log"
"os"
"strings"
)
@@ -49,10 +50,20 @@ func (c Command) Run(env packer.Environment, args []string) int {
tplF.Close()
// Run the template through the various fixers
fixers := []Fixer{Fixers["iso-md5"]}
fixers := []string{
"iso-md5",
"createtime",
}
input := templateData
for _, fixer := range fixers {
for _, name := range fixers {
var err error
fixer, ok := Fixers[name]
if !ok {
panic("fixer not found: " + name)
}
log.Printf("Running fixer: %s", name)
input, err = fixer.Fix(input)
if err != nil {
env.Ui().Error(fmt.Sprintf("Error fixing: %s", err))
+2 -1
View File
@@ -13,6 +13,7 @@ var Fixers map[string]Fixer
func init() {
Fixers = map[string]Fixer{
"iso-md5": new(FixerISOMD5),
"iso-md5": new(FixerISOMD5),
"createtime": new(FixerCreateTime),
}
}
+51
View File
@@ -0,0 +1,51 @@
package fix
import (
"github.com/mitchellh/mapstructure"
"regexp"
)
// FixerCreateTime is a Fixer that replaces the ".CreateTime" template
// calls with "{{timestamp}"
type FixerCreateTime struct{}
func (FixerCreateTime) Fix(input map[string]interface{}) (map[string]interface{}, error) {
// Our template type we'll use for this fixer only
type template struct {
Builders []map[string]interface{}
}
// Decode the input into our structure, if we can
var tpl template
if err := mapstructure.Decode(input, &tpl); err != nil {
return nil, err
}
badKeys := []string{
"ami_name",
"bundle_prefix",
"snapshot_name",
}
re := regexp.MustCompile(`{{\s*\.CreateTime\s*}}`)
// Go through each builder and replace CreateTime if we can
for _, builder := range tpl.Builders {
for _, key := range badKeys {
raw, ok := builder[key]
if !ok {
continue
}
v, ok := raw.(string)
if !ok {
continue
}
builder[key] = re.ReplaceAllString(v, "{{timestamp}}")
}
}
input["builders"] = tpl.Builders
return input, nil
}
+45
View File
@@ -0,0 +1,45 @@
package fix
import (
"reflect"
"testing"
)
func TestFixerCreateTime_Impl(t *testing.T) {
var raw interface{}
raw = new(FixerCreateTime)
if _, ok := raw.(Fixer); !ok {
t.Fatalf("must be a Fixer")
}
}
func TestFixerCreateTime_Fix(t *testing.T) {
var f FixerCreateTime
input := map[string]interface{}{
"builders": []interface{}{
map[string]string{
"type": "foo",
"ami_name": "{{.CreateTime}} foo",
},
},
}
expected := map[string]interface{}{
"builders": []map[string]interface{}{
map[string]interface{}{
"type": "foo",
"ami_name": "{{timestamp}} foo",
},
},
}
output, err := f.Fix(input)
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(output, expected) {
t.Fatalf("unexpected: %#v\nexpected: %#v\n", output, expected)
}
}
+21 -19
View File
@@ -3,8 +3,8 @@ package validate
import (
"flag"
"fmt"
cmdcommon "github.com/mitchellh/packer/common/command"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
"strings"
)
@@ -17,10 +17,12 @@ func (Command) Help() string {
func (c Command) Run(env packer.Environment, args []string) int {
var cfgSyntaxOnly bool
buildOptions := new(cmdcommon.BuildOptions)
cmdFlags := flag.NewFlagSet("validate", flag.ContinueOnError)
cmdFlags.Usage = func() { env.Ui().Say(c.Help()) }
cmdFlags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only")
cmdcommon.BuildOptionFlags(cmdFlags, buildOptions)
if err := cmdFlags.Parse(args); err != nil {
return 1
}
@@ -31,17 +33,24 @@ func (c Command) Run(env packer.Environment, args []string) int {
return 1
}
// Read the file into a byte array so that we can parse the template
log.Printf("Reading template: %s", args[0])
tplData, err := ioutil.ReadFile(args[0])
if err := buildOptions.Validate(); err != nil {
env.Ui().Error(err.Error())
env.Ui().Error("")
env.Ui().Error(c.Help())
return 1
}
userVars, err := buildOptions.AllUserVars()
if err != nil {
env.Ui().Error(fmt.Sprintf("Failed to read template file: %s", err))
env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err))
env.Ui().Error("")
env.Ui().Error(c.Help())
return 1
}
// Parse the template into a machine-usable format
log.Println("Parsing template...")
tpl, err := packer.ParseTemplate(tplData)
log.Printf("Reading template: %s", args[0])
tpl, err := packer.ParseTemplateFile(args[0])
if err != nil {
env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err))
return 1
@@ -63,23 +72,16 @@ func (c Command) Run(env packer.Environment, args []string) int {
}
// Otherwise, get all the builds
buildNames := tpl.BuildNames()
builds := make([]packer.Build, 0, len(buildNames))
for _, buildName := range buildNames {
log.Printf("Creating build from template for: %s", buildName)
build, err := tpl.Build(buildName, components)
if err != nil {
errs = append(errs, fmt.Errorf("Build '%s': %s", buildName, err))
continue
}
builds = append(builds, build)
builds, err := buildOptions.Builds(tpl, components)
if err != nil {
env.Ui().Error(err.Error())
return 1
}
// Check the configuration of all builds
for _, b := range builds {
log.Printf("Preparing build: %s", b.Name())
err := b.Prepare()
err := b.Prepare(userVars)
if err != nil {
errs = append(errs, fmt.Errorf("Errors validating build '%s'. %s", b.Name(), err))
}
+5 -1
View File
@@ -12,5 +12,9 @@ Usage: packer validate [options] TEMPLATE
Options:
-syntax-only Only check syntax. Do not verify config of the template.
-syntax-only Only check syntax. Do not verify config of the template.
-except=foo,bar,baz Validate all builds other than these
-only=foo,bar,baz Validate only these builds
-var 'key=value' Variable for templates, can be used multiple times.
-var-file=path JSON file containing user variables.
`
+39
View File
@@ -0,0 +1,39 @@
package command
import (
"flag"
"fmt"
"strings"
)
// BuildOptionFlags sets the proper command line flags needed for
// build options.
func BuildOptionFlags(fs *flag.FlagSet, f *BuildOptions) {
fs.Var((*SliceValue)(&f.Except), "except", "build all builds except these")
fs.Var((*SliceValue)(&f.Only), "only", "only build the given builds by name")
fs.Var((*userVarValue)(&f.UserVars), "var", "specify a user variable")
fs.Var((*AppendSliceValue)(&f.UserVarFiles), "var-file", "file with user variables")
}
// userVarValue is a flag.Value that parses out user variables in
// the form of 'key=value' and sets it on this map.
type userVarValue map[string]string
func (v *userVarValue) String() string {
return ""
}
func (v *userVarValue) Set(raw string) error {
idx := strings.Index(raw, "=")
if idx == -1 {
return fmt.Errorf("No '=' value in arg: %s", raw)
}
if *v == nil {
*v = make(map[string]string)
}
key, value := raw[0:idx], raw[idx+1:]
(*v)[key] = value
return nil
}
+104
View File
@@ -0,0 +1,104 @@
package command
import (
"flag"
"reflect"
"testing"
)
func TestBuildOptionFlags(t *testing.T) {
opts := new(BuildOptions)
fs := flag.NewFlagSet("test", flag.ContinueOnError)
BuildOptionFlags(fs, opts)
args := []string{
"-except=foo,bar,baz",
"-only=a,b",
"-var=foo=bar",
"-var", "bar=baz",
"-var=foo=bang",
"-var-file=foo",
"-var-file=bar",
}
err := fs.Parse(args)
if err != nil {
t.Fatalf("err: %s", err)
}
expected := []string{"foo", "bar", "baz"}
if !reflect.DeepEqual(opts.Except, expected) {
t.Fatalf("bad: %#v", opts.Except)
}
expected = []string{"a", "b"}
if !reflect.DeepEqual(opts.Only, expected) {
t.Fatalf("bad: %#v", opts.Only)
}
if len(opts.UserVars) != 2 {
t.Fatalf("bad: %#v", opts.UserVars)
}
if opts.UserVars["foo"] != "bang" {
t.Fatalf("bad: %#v", opts.UserVars)
}
if opts.UserVars["bar"] != "baz" {
t.Fatalf("bad: %#v", opts.UserVars)
}
expected = []string{"foo", "bar"}
if !reflect.DeepEqual(opts.UserVarFiles, expected) {
t.Fatalf("bad: %#v", opts.UserVarFiles)
}
}
func TestUserVarValue_implements(t *testing.T) {
var raw interface{}
raw = new(userVarValue)
if _, ok := raw.(flag.Value); !ok {
t.Fatalf("userVarValue should be a Value")
}
}
func TestUserVarValueSet(t *testing.T) {
sv := new(userVarValue)
err := sv.Set("key=value")
if err != nil {
t.Fatalf("err: %s", err)
}
vars := map[string]string(*sv)
if vars["key"] != "value" {
t.Fatalf("Bad: %#v", vars)
}
// Empty value
err = sv.Set("key=")
if err != nil {
t.Fatalf("err: %s", err)
}
vars = map[string]string(*sv)
if vars["key"] != "" {
t.Fatalf("Bad: %#v", vars)
}
// Equal in value
err = sv.Set("key=foo=bar")
if err != nil {
t.Fatalf("err: %s", err)
}
vars = map[string]string(*sv)
if vars["key"] != "foo=bar" {
t.Fatalf("Bad: %#v", vars)
}
// No equal
err = sv.Set("key")
if err == nil {
t.Fatal("should have error")
}
}
+34
View File
@@ -0,0 +1,34 @@
package command
import "strings"
// AppendSliceValue implements the flag.Value interface and allows multiple
// calls to the same variable to append a list.
type AppendSliceValue []string
func (s *AppendSliceValue) String() string {
return strings.Join(*s, ",")
}
func (s *AppendSliceValue) Set(value string) error {
if *s == nil {
*s = make([]string, 0, 1)
}
*s = append(*s, value)
return nil
}
// SliceValue implements the flag.Value interface and allows a list of
// strings to be given on the command line and properly parsed into a slice
// of strings internally.
type SliceValue []string
func (s *SliceValue) String() string {
return strings.Join(*s, ",")
}
func (s *SliceValue) Set(value string) error {
*s = strings.Split(value, ",")
return nil
}
+54
View File
@@ -0,0 +1,54 @@
package command
import (
"flag"
"reflect"
"testing"
)
func TestAppendSliceValue_implements(t *testing.T) {
var raw interface{}
raw = new(AppendSliceValue)
if _, ok := raw.(flag.Value); !ok {
t.Fatalf("AppendSliceValue should be a Value")
}
}
func TestAppendSliceValueSet(t *testing.T) {
sv := new(AppendSliceValue)
err := sv.Set("foo")
if err != nil {
t.Fatalf("err: %s", err)
}
err = sv.Set("bar")
if err != nil {
t.Fatalf("err: %s", err)
}
expected := []string{"foo", "bar"}
if !reflect.DeepEqual([]string(*sv), expected) {
t.Fatalf("Bad: %#v", sv)
}
}
func TestSliceValue_implements(t *testing.T) {
var raw interface{}
raw = new(SliceValue)
if _, ok := raw.(flag.Value); !ok {
t.Fatalf("SliceValue should be a Value")
}
}
func TestSliceValueSet(t *testing.T) {
sv := new(SliceValue)
err := sv.Set("foo,bar,baz")
if err != nil {
t.Fatalf("err: %s", err)
}
expected := []string{"foo", "bar", "baz"}
if !reflect.DeepEqual([]string(*sv), expected) {
t.Fatalf("Bad: %#v", sv)
}
}
+125
View File
@@ -0,0 +1,125 @@
package command
import (
"errors"
"fmt"
jsonutil "github.com/mitchellh/packer/common/json"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
"os"
)
// BuildOptions is a set of options related to builds that can be set
// from the command line.
type BuildOptions struct {
UserVarFiles []string
UserVars map[string]string
Except []string
Only []string
}
// Validate validates the options
func (f *BuildOptions) Validate() error {
if len(f.Except) > 0 && len(f.Only) > 0 {
return errors.New("Only one of '-except' or '-only' may be specified.")
}
if len(f.UserVarFiles) > 0 {
for _, path := range f.UserVarFiles {
if _, err := os.Stat(path); err != nil {
return fmt.Errorf("Cannot access: %s", path)
}
}
}
return nil
}
// AllUserVars returns the user variables, compiled from both the
// file paths and the vars on the command line.
func (f *BuildOptions) AllUserVars() (map[string]string, error) {
all := make(map[string]string)
// Copy in the variables from the files
for _, path := range f.UserVarFiles {
fileVars, err := readFileVars(path)
if err != nil {
return nil, err
}
for k, v := range fileVars {
all[k] = v
}
}
// Copy in the command-line vars
for k, v := range f.UserVars {
all[k] = v
}
return all, nil
}
// Builds returns the builds out of the given template that pass the
// configured options.
func (f *BuildOptions) Builds(t *packer.Template, cf *packer.ComponentFinder) ([]packer.Build, error) {
buildNames := t.BuildNames()
builds := make([]packer.Build, 0, len(buildNames))
for _, buildName := range buildNames {
if len(f.Except) > 0 {
found := false
for _, except := range f.Except {
if buildName == except {
found = true
break
}
}
if found {
log.Printf("Skipping build '%s' because specified by -except.", buildName)
continue
}
}
if len(f.Only) > 0 {
found := false
for _, only := range f.Only {
if buildName == only {
found = true
break
}
}
if !found {
log.Printf("Skipping build '%s' because not specified by -only.", buildName)
continue
}
}
log.Printf("Creating build: %s", buildName)
build, err := t.Build(buildName, cf)
if err != nil {
return nil, fmt.Errorf("Failed to create build '%s': \n\n%s", buildName, err)
}
builds = append(builds, build)
}
return builds, nil
}
func readFileVars(path string) (map[string]string, error) {
bytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
vars := make(map[string]string)
err = jsonutil.Unmarshal(bytes, &vars)
if err != nil {
return nil, err
}
return vars, nil
}
+53
View File
@@ -0,0 +1,53 @@
package command
import (
"testing"
)
func TestBuildOptionsValidate(t *testing.T) {
bf := new(BuildOptions)
err := bf.Validate()
if err != nil {
t.Fatalf("err: %s", err)
}
// Both set
bf.Except = make([]string, 1)
bf.Only = make([]string, 1)
err = bf.Validate()
if err == nil {
t.Fatal("should error")
}
// One set
bf.Except = make([]string, 1)
bf.Only = make([]string, 0)
err = bf.Validate()
if err != nil {
t.Fatalf("err: %s", err)
}
bf.Except = make([]string, 0)
bf.Only = make([]string, 1)
err = bf.Validate()
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestBuildOptionsValidate_userVarFiles(t *testing.T) {
bf := new(BuildOptions)
err := bf.Validate()
if err != nil {
t.Fatalf("err: %s", err)
}
// Non-existent file
bf.UserVarFiles = []string{"ireallyshouldntexistanywhere"}
err = bf.Validate()
if err == nil {
t.Fatal("should error")
}
}
+40
View File
@@ -0,0 +1,40 @@
package json
import (
"bytes"
"encoding/json"
"fmt"
)
// Unmarshal is wrapper around json.Unmarshal that returns user-friendly
// errors when there are syntax errors.
func Unmarshal(data []byte, i interface{}) error {
err := json.Unmarshal(data, i)
if err != nil {
syntaxErr, ok := err.(*json.SyntaxError)
if !ok {
return err
}
// We have a syntax error. Extract out the line number and friends.
// https://groups.google.com/forum/#!topic/golang-nuts/fizimmXtVfc
newline := []byte{'\x0a'}
// Calculate the start/end position of the line where the error is
start := bytes.LastIndex(data[:syntaxErr.Offset], newline) + 1
end := len(data)
if idx := bytes.Index(data[start:], newline); idx >= 0 {
end = start + idx
}
// Count the line number we're on plus the offset in the line
line := bytes.Count(data[:start], newline) + 1
pos := int(syntaxErr.Offset) - start - 1
err = fmt.Errorf("Error in line %d, char %d: %s\n%s",
line, pos, syntaxErr, data[start:end])
return err
}
return nil
}
+1
View File
@@ -0,0 +1 @@
package json
+5 -3
View File
@@ -4,7 +4,9 @@ package common
// are sent by packer, properly tagged already so mapstructure can load
// them. Embed this structure into your configuration class to get it.
type PackerConfig struct {
PackerBuildName string `mapstructure:"packer_build_name"`
PackerDebug bool `mapstructure:"packer_debug"`
PackerForce bool `mapstructure:"packer_force"`
PackerBuildName string `mapstructure:"packer_build_name"`
PackerBuilderType string `mapstructure:"packer_builder_type"`
PackerDebug bool `mapstructure:"packer_debug"`
PackerForce bool `mapstructure:"packer_force"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables"`
}
+81
View File
@@ -0,0 +1,81 @@
package common
import (
"bytes"
"fmt"
"strconv"
"text/template"
"time"
)
// Template processes string data as a text/template with some common
// elements and functions available. Plugin creators should process as
// many fields as possible through this.
type Template struct {
UserVars map[string]string
root *template.Template
i int
}
// NewTemplate creates a new template processor.
func NewTemplate() (*Template, error) {
result := &Template{
UserVars: make(map[string]string),
}
result.root = template.New("configTemplateRoot")
result.root.Funcs(template.FuncMap{
"timestamp": templateTimestamp,
"user": result.templateUser,
})
return result, nil
}
// Process processes a single string, compiling and executing the template.
func (t *Template) Process(s string, data interface{}) (string, error) {
tpl, err := t.root.New(t.nextTemplateName()).Parse(s)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
if err := tpl.Execute(buf, data); err != nil {
return "", err
}
return buf.String(), nil
}
// Validate the template.
func (t *Template) Validate(s string) error {
root, err := t.root.Clone()
if err != nil {
return err
}
_, err = root.New("template").Parse(s)
return err
}
func (t *Template) nextTemplateName() string {
name := fmt.Sprintf("tpl%d", t.i)
t.i++
return name
}
// User is the function exposed as "user" within the templates and
// looks up user variables.
func (t *Template) templateUser(n string) (string, error) {
result, ok := t.UserVars[n]
if !ok {
return "", fmt.Errorf("uknown user var: %s", n)
}
return result, nil
}
func templateTimestamp() string {
return strconv.FormatInt(time.Now().UTC().Unix(), 10)
}
+67
View File
@@ -0,0 +1,67 @@
package common
import (
"math"
"strconv"
"testing"
"time"
)
func TestTemplateProcess_timestamp(t *testing.T) {
tpl, err := NewTemplate()
if err != nil {
t.Fatalf("err: %s", err)
}
result, err := tpl.Process(`{{timestamp}}`, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
val, err := strconv.ParseInt(result, 10, 64)
if err != nil {
t.Fatalf("err: %s", err)
}
currentTime := time.Now().UTC().Unix()
if math.Abs(float64(currentTime-val)) > 10 {
t.Fatalf("val: %d (current: %d)", val, currentTime)
}
}
func TestTemplateProcess_user(t *testing.T) {
tpl, err := NewTemplate()
if err != nil {
t.Fatalf("err: %s", err)
}
tpl.UserVars["foo"] = "bar"
result, err := tpl.Process(`{{user "foo"}}`, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if result != "bar" {
t.Fatalf("bad: %s", result)
}
}
func TestTemplateValidate(t *testing.T) {
tpl, err := NewTemplate()
if err != nil {
t.Fatalf("err: %s", err)
}
// Valid
err = tpl.Validate(`{{user "foo"}}`)
if err != nil {
t.Fatalf("err: %s", err)
}
// Invalid
err = tpl.Validate(`{{idontexist}}`)
if err == nil {
t.Fatal("should have error")
}
}
+17
View File
@@ -10,6 +10,9 @@ import (
"io"
)
// SimpleKeychain makes it easy to use private keys in order to connect
// via SSH, since the interface exposed by Go isn't the easiest to use
// right away.
type SimpleKeychain struct {
keys []interface{}
}
@@ -26,6 +29,20 @@ func (k *SimpleKeychain) AddPEMKey(key string) (err error) {
return
}
// AddPEMKeyPassword adds a PEM encoded private key that is protected by
// a password to the keychain.
func (k *SimpleKeychain) AddPEMKeyPassword(key string, password string) (err error) {
block, _ := pem.Decode([]byte(key))
bytes, _ := x509.DecryptPEMBlock(block, []byte(password))
rsakey, err := x509.ParsePKCS1PrivateKey(bytes)
if err != nil {
return
}
k.keys = append(k.keys, rsakey)
return
}
// Key method for ssh.ClientKeyring interface
func (k *SimpleKeychain) Key(i int) (interface{}, error) {
if i < 0 || i >= len(k.keys) {
+30 -1
View File
@@ -76,8 +76,13 @@ func main() {
log.Printf("Setting cache directory: %s", cacheDir)
cache := &packer.FileCache{CacheDir: cacheDir}
// Determine if we're in machine-readable mode by mucking around with
// the arguments...
args, machineReadable := extractMachineReadable(os.Args[1:])
defer plugin.CleanupClients()
// Create the environment configuration
envConfig := packer.DefaultEnvironmentConfig()
envConfig.Cache = cache
envConfig.Commands = config.CommandNames()
@@ -86,18 +91,25 @@ func main() {
envConfig.Components.Hook = config.LoadHook
envConfig.Components.PostProcessor = config.LoadPostProcessor
envConfig.Components.Provisioner = config.LoadProvisioner
if machineReadable {
envConfig.Ui = &packer.MachineReadableUi{
Writer: os.Stdout,
}
}
env, err := packer.NewEnvironment(envConfig)
if err != nil {
fmt.Fprintf(os.Stderr, "Packer initialization error: \n\n%s\n", err)
plugin.CleanupClients()
os.Exit(1)
}
setupSignalHandlers(env)
exitCode, err := env.Cli(os.Args[1:])
exitCode, err := env.Cli(args)
if err != nil {
fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error())
plugin.CleanupClients()
os.Exit(1)
}
@@ -105,6 +117,23 @@ func main() {
os.Exit(exitCode)
}
// extractMachineReadable checks the args for the machine readable
// flag and returns whether or not it is on. It modifies the args
// to remove this flag.
func extractMachineReadable(args []string) ([]string, bool) {
for i, arg := range args {
if arg == "-machine-readable" {
// We found it. Slice it out.
result := make([]string, len(args)-1)
copy(result, args[:i])
copy(result[i:], args[i+1:])
return result, true
}
}
return args, false
}
func loadConfig() (*config, error) {
var config config
if err := decodeConfig(bytes.NewBufferString(defaultConfig), &config); err != nil {
+46 -16
View File
@@ -23,6 +23,10 @@ const (
// This is the key in configurations that is set to "true" when Packer
// force build is enabled.
ForceConfigKey = "packer_force"
// This key contains a map[string]string of the user variables for
// template processing.
UserVariablesConfigKey = "packer_user_variables"
)
// A Build represents a single job within Packer that is responsible for
@@ -35,7 +39,7 @@ type Build interface {
// Prepare configures the various components of this build and reports
// any errors in doing so (such as syntax errors, validation errors, etc.)
Prepare() error
Prepare(v map[string]string) error
// Run runs the actual builder, returning an artifact implementation
// of what is built. If anything goes wrong, an error is returned.
@@ -73,6 +77,7 @@ type coreBuild struct {
hooks map[string][]Hook
postProcessors [][]coreBuildPostProcessor
provisioners []coreBuildProvisioner
variables map[string]string
debug bool
force bool
@@ -102,8 +107,9 @@ func (b *coreBuild) Name() string {
}
// Prepare prepares the build by doing some initialization for the builder
// and any hooks. This _must_ be called prior to Run.
func (b *coreBuild) Prepare() (err error) {
// and any hooks. This _must_ be called prior to Run. The parameter is the
// overrides for the variables within the template (if any).
func (b *coreBuild) Prepare(userVars map[string]string) (err error) {
b.l.Lock()
defer b.l.Unlock()
@@ -113,11 +119,37 @@ func (b *coreBuild) Prepare() (err error) {
b.prepareCalled = true
// Compile the variables
variables := make(map[string]string)
for k, v := range b.variables {
variables[k] = v
}
if userVars != nil {
errs := make([]error, 0)
for k, v := range userVars {
if _, ok := variables[k]; !ok {
errs = append(
errs, fmt.Errorf("Unknown user variable: %s", k))
continue
}
variables[k] = v
}
if len(errs) > 0 {
return &MultiError{
Errors: errs,
}
}
}
packerConfig := map[string]interface{}{
BuildNameConfigKey: b.name,
BuilderTypeConfigKey: b.builderType,
DebugConfigKey: b.debug,
ForceConfigKey: b.force,
BuildNameConfigKey: b.name,
BuilderTypeConfigKey: b.builderType,
DebugConfigKey: b.debug,
ForceConfigKey: b.force,
UserVariablesConfigKey: variables,
}
// Prepare the builder
@@ -181,11 +213,10 @@ func (b *coreBuild) Run(originalUi Ui, cache Cache) ([]Artifact, error) {
hook := &DispatchHook{hooks}
artifacts := make([]Artifact, 0, 1)
// The builder just has a normal Ui, but prefixed
builderUi := &PrefixedUi{
fmt.Sprintf("==> %s", b.Name()),
fmt.Sprintf(" %s", b.Name()),
originalUi,
// The builder just has a normal Ui, but targetted
builderUi := &TargettedUi{
Target: b.Name(),
Ui: originalUi,
}
log.Printf("Running builder: %s", b.builderType)
@@ -208,10 +239,9 @@ PostProcessorRunSeqLoop:
for _, ppSeq := range b.postProcessors {
priorArtifact := builderArtifact
for i, corePP := range ppSeq {
ppUi := &PrefixedUi{
fmt.Sprintf("==> %s (%s)", b.Name(), corePP.processorType),
fmt.Sprintf(" %s (%s)", b.Name(), corePP.processorType),
originalUi,
ppUi := &TargettedUi{
Target: fmt.Sprintf("%s (%s)", b.Name(), corePP.processorType),
Ui: originalUi,
}
builderUi.Say(fmt.Sprintf("Running post-processor: %s", corePP.processorType))
+81 -23
View File
@@ -23,6 +23,7 @@ func testBuild() *coreBuild {
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp"}, "testPP", 42, true},
},
},
variables: make(map[string]string),
}
}
@@ -30,6 +31,15 @@ func testBuilder() *TestBuilder {
return &TestBuilder{}
}
func testDefaultPackerConfig() map[string]interface{} {
return map[string]interface{}{
BuildNameConfigKey: "test",
BuilderTypeConfigKey: "foo",
DebugConfigKey: false,
ForceConfigKey: false,
UserVariablesConfigKey: make(map[string]string),
}
}
func TestBuild_Name(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
@@ -39,18 +49,12 @@ func TestBuild_Name(t *testing.T) {
func TestBuild_Prepare(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
packerConfig := map[string]interface{}{
BuildNameConfigKey: "test",
BuilderTypeConfigKey: "foo",
DebugConfigKey: false,
ForceConfigKey: false,
}
packerConfig := testDefaultPackerConfig()
build := testBuild()
builder := build.builder.(*TestBuilder)
build.Prepare()
build.Prepare(nil)
assert.True(builder.prepareCalled, "prepare should be called")
assert.Equal(builder.prepareConfig, []interface{}{42, packerConfig}, "prepare config should be 42")
@@ -67,7 +71,7 @@ func TestBuild_Prepare(t *testing.T) {
func TestBuild_Prepare_Twice(t *testing.T) {
build := testBuild()
if err := build.Prepare(); err != nil {
if err := build.Prepare(nil); err != nil {
t.Fatalf("bad error: %s", err)
}
@@ -82,24 +86,20 @@ func TestBuild_Prepare_Twice(t *testing.T) {
}
}()
build.Prepare()
build.Prepare(nil)
}
func TestBuild_Prepare_Debug(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
packerConfig := map[string]interface{}{
BuildNameConfigKey: "test",
BuilderTypeConfigKey: "foo",
DebugConfigKey: true,
ForceConfigKey: false,
}
packerConfig := testDefaultPackerConfig()
packerConfig[DebugConfigKey] = true
build := testBuild()
builder := build.builder.(*TestBuilder)
build.SetDebug(true)
build.Prepare()
build.Prepare(nil)
assert.True(builder.prepareCalled, "prepare should be called")
assert.Equal(builder.prepareConfig, []interface{}{42, packerConfig}, "prepare config should be 42")
@@ -109,6 +109,64 @@ func TestBuild_Prepare_Debug(t *testing.T) {
assert.Equal(prov.prepConfigs, []interface{}{42, packerConfig}, "prepare should be called with proper config")
}
func TestBuildPrepare_variables_default(t *testing.T) {
packerConfig := testDefaultPackerConfig()
packerConfig[UserVariablesConfigKey] = map[string]string{
"foo": "bar",
}
build := testBuild()
build.variables["foo"] = "bar"
builder := build.builder.(*TestBuilder)
err := build.Prepare(nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if !builder.prepareCalled {
t.Fatal("prepare should be called")
}
if !reflect.DeepEqual(builder.prepareConfig[1], packerConfig) {
t.Fatalf("prepare bad: %#v", builder.prepareConfig[1])
}
}
func TestBuildPrepare_variables_nonexist(t *testing.T) {
build := testBuild()
build.variables["foo"] = "bar"
err := build.Prepare(map[string]string{"bar": "baz"})
if err == nil {
t.Fatal("should have had error")
}
}
func TestBuildPrepare_variables_override(t *testing.T) {
packerConfig := testDefaultPackerConfig()
packerConfig[UserVariablesConfigKey] = map[string]string{
"foo": "baz",
}
build := testBuild()
build.variables["foo"] = "bar"
builder := build.builder.(*TestBuilder)
err := build.Prepare(map[string]string{"foo": "baz"})
if err != nil {
t.Fatalf("err: %s", err)
}
if !builder.prepareCalled {
t.Fatal("prepare should be called")
}
if !reflect.DeepEqual(builder.prepareConfig[1], packerConfig) {
t.Fatalf("prepare bad: %#v", builder.prepareConfig[1])
}
}
func TestBuild_Run(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
@@ -116,7 +174,7 @@ func TestBuild_Run(t *testing.T) {
ui := testUi()
build := testBuild()
build.Prepare()
build.Prepare(nil)
artifacts, err := build.Run(ui, cache)
assert.Nil(err, "should not error")
assert.Equal(len(artifacts), 2, "should have two artifacts")
@@ -152,7 +210,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
build := testBuild()
build.postProcessors = [][]coreBuildPostProcessor{}
build.Prepare()
build.Prepare(nil)
artifacts, err := build.Run(ui, cache)
if err != nil {
t.Fatalf("err: %s", err)
@@ -177,7 +235,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
},
}
build.Prepare()
build.Prepare(nil)
artifacts, err = build.Run(ui, cache)
if err != nil {
t.Fatalf("err: %s", err)
@@ -205,7 +263,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
},
}
build.Prepare()
build.Prepare(nil)
artifacts, err = build.Run(ui, cache)
if err != nil {
t.Fatalf("err: %s", err)
@@ -235,7 +293,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
},
}
build.Prepare()
build.Prepare(nil)
artifacts, err = build.Run(ui, cache)
if err != nil {
t.Fatalf("err: %s", err)
@@ -262,7 +320,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
},
}
build.Prepare()
build.Prepare(nil)
artifacts, err = build.Run(ui, cache)
if err != nil {
t.Fatalf("err: %s", err)
+1 -1
View File
@@ -47,7 +47,7 @@ func TestRemoteCmd_StartWithUi(t *testing.T) {
Stdout: rcOutput,
}
testUi := &ReaderWriterUi{
testUi := &BasicUi{
Reader: new(bytes.Buffer),
Writer: uiOutput,
}
+7 -1
View File
@@ -73,7 +73,7 @@ type EnvironmentConfig struct {
func DefaultEnvironmentConfig() *EnvironmentConfig {
config := &EnvironmentConfig{}
config.Commands = make([]string, 0)
config.Ui = &ReaderWriterUi{
config.Ui = &BasicUi{
Reader: os.Stdin,
Writer: os.Stdout,
}
@@ -293,12 +293,18 @@ func (e *coreEnvironment) printHelp() {
synopsis = command.Synopsis()
}
// Machine-readable output of the available command
e.ui.Machine("command", key, synopsis)
// Pad the key with spaces so that they're all the same width
key = fmt.Sprintf("%v%v", key, strings.Repeat(" ", maxKeyLen-len(key)))
// Output the command and the synopsis
e.ui.Say(fmt.Sprintf(" %v %v", key, synopsis))
}
e.ui.Say("\nGlobally recognized options:")
e.ui.Say(" -machine-readable Machine-readable output format.")
}
// Returns the UI for the environment. The UI is the interface that should
+5 -5
View File
@@ -19,7 +19,7 @@ func init() {
func testEnvironment() Environment {
config := DefaultEnvironmentConfig()
config.Ui = &ReaderWriterUi{
config.Ui = &BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
}
@@ -45,8 +45,8 @@ func TestEnvironment_DefaultConfig_Ui(t *testing.T) {
config := DefaultEnvironmentConfig()
assert.NotNil(config.Ui, "default UI should not be nil")
rwUi, ok := config.Ui.(*ReaderWriterUi)
assert.True(ok, "default UI should be ReaderWriterUi")
rwUi, ok := config.Ui.(*BasicUi)
assert.True(ok, "default UI should be BasicUi")
assert.Equal(rwUi.Writer, os.Stdout, "default UI should go to stdout")
assert.Equal(rwUi.Reader, os.Stdin, "default UI should read from stdin")
}
@@ -175,7 +175,7 @@ func TestEnvironment_DefaultCli_Help(t *testing.T) {
// A little lambda to help us test the output actually contains help
testOutput := func() {
buffer := defaultEnv.Ui().(*ReaderWriterUi).Writer.(*bytes.Buffer)
buffer := defaultEnv.Ui().(*BasicUi).Writer.(*bytes.Buffer)
output := buffer.String()
buffer.Reset()
assert.True(strings.Contains(output, "usage: packer"), "should print help")
@@ -341,7 +341,7 @@ func TestEnvironmentProvisioner_Error(t *testing.T) {
func TestEnvironment_SettingUi(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
ui := &ReaderWriterUi{
ui := &BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
}
+4 -4
View File
@@ -30,8 +30,8 @@ func (b *build) Name() (result string) {
return
}
func (b *build) Prepare() (err error) {
if cerr := b.client.Call("Build.Prepare", new(interface{}), &err); cerr != nil {
func (b *build) Prepare(v map[string]string) (err error) {
if cerr := b.client.Call("Build.Prepare", v, &err); cerr != nil {
return cerr
}
@@ -86,8 +86,8 @@ func (b *BuildServer) Name(args *interface{}, reply *string) error {
return nil
}
func (b *BuildServer) Prepare(args interface{}, reply *error) error {
*reply = b.build.Prepare()
func (b *BuildServer) Prepare(v map[string]string, reply *error) error {
*reply = b.build.Prepare(v)
return nil
}
+11 -2
View File
@@ -13,6 +13,7 @@ var testBuildArtifact = &testArtifact{}
type testBuild struct {
nameCalled bool
prepareCalled bool
prepareVars map[string]string
runCalled bool
runCache packer.Cache
runUi packer.Ui
@@ -28,8 +29,9 @@ func (b *testBuild) Name() string {
return "name"
}
func (b *testBuild) Prepare() error {
func (b *testBuild) Prepare(v map[string]string) error {
b.prepareCalled = true
b.prepareVars = v
return nil
}
@@ -78,8 +80,15 @@ func TestBuildRPC(t *testing.T) {
assert.True(b.nameCalled, "name should be called")
// Test Prepare
bClient.Prepare()
bClient.Prepare(map[string]string{"foo": "bar"})
assert.True(b.prepareCalled, "prepare should be called")
if len(b.prepareVars) != 1 {
t.Fatalf("bad vars: %#v", b.prepareVars)
}
if b.prepareVars["foo"] != "bar" {
t.Fatalf("bad vars: %#v", b.prepareVars)
}
// Test Run
cache := new(testCache)
+1
View File
@@ -4,6 +4,7 @@ import "encoding/gob"
func init() {
gob.Register(new(map[string]interface{}))
gob.Register(new(map[string]string))
gob.Register(make([]interface{}, 0))
gob.Register(new(BasicError))
}
+24
View File
@@ -17,6 +17,12 @@ type UiServer struct {
ui packer.Ui
}
// The arguments sent to Ui.Machine
type UiMachineArgs struct {
Category string
Args []string
}
func (u *Ui) Ask(query string) (result string, err error) {
err = u.client.Call("Ui.Ask", query, &result)
return
@@ -28,6 +34,17 @@ func (u *Ui) Error(message string) {
}
}
func (u *Ui) Machine(t string, args ...string) {
rpcArgs := &UiMachineArgs{
Category: t,
Args: args,
}
if err := u.client.Call("Ui.Machine", rpcArgs, new(interface{})); err != nil {
panic(err)
}
}
func (u *Ui) Message(message string) {
if err := u.client.Call("Ui.Message", message, new(interface{})); err != nil {
panic(err)
@@ -52,6 +69,13 @@ func (u *UiServer) Error(message *string, reply *interface{}) error {
return nil
}
func (u *UiServer) Machine(args *UiMachineArgs, reply *interface{}) error {
u.ui.Machine(args.Category, args.Args...)
*reply = nil
return nil
}
func (u *UiServer) Message(message *string, reply *interface{}) error {
u.ui.Message(*message)
*reply = nil
+24
View File
@@ -3,6 +3,7 @@ package rpc
import (
"cgl.tideland.biz/asserts"
"net/rpc"
"reflect"
"testing"
)
@@ -11,6 +12,9 @@ type testUi struct {
askQuery string
errorCalled bool
errorMessage string
machineCalled bool
machineType string
machineArgs []string
messageCalled bool
messageMessage string
sayCalled bool
@@ -28,6 +32,12 @@ func (u *testUi) Error(message string) {
u.errorMessage = message
}
func (u *testUi) Machine(t string, args ...string) {
u.machineCalled = true
u.machineType = t
u.machineArgs = args
}
func (u *testUi) Message(message string) {
u.messageCalled = true
u.messageMessage = message
@@ -72,4 +82,18 @@ func TestUiRPC(t *testing.T) {
uiClient.Say("message")
assert.Equal(ui.sayMessage, "message", "message should be correct")
uiClient.Machine("foo", "bar", "baz")
if !ui.machineCalled {
t.Fatal("machine should be called")
}
if ui.machineType != "foo" {
t.Fatalf("bad type: %#v", ui.machineType)
}
expected := []string{"bar", "baz"}
if !reflect.DeepEqual(ui.machineArgs, expected) {
t.Fatalf("bad: %#v", ui.machineArgs)
}
}
+23 -26
View File
@@ -1,10 +1,10 @@
package packer
import (
"bytes"
"encoding/json"
"fmt"
"github.com/mitchellh/mapstructure"
jsonutil "github.com/mitchellh/packer/common/json"
"io/ioutil"
"sort"
)
@@ -13,6 +13,7 @@ import (
// "interface{}" pointers since we actually don't know what their contents
// are until we read the "type" field.
type rawTemplate struct {
Variables map[string]string
Builders []map[string]interface{}
Hooks map[string][]string
Provisioners []map[string]interface{}
@@ -22,6 +23,7 @@ type rawTemplate struct {
// The Template struct represents a parsed template, parsed into the most
// completed form it can be without additional processing by the caller.
type Template struct {
Variables map[string]string
Builders map[string]rawBuilderConfig
Hooks map[string][]string
PostProcessors [][]rawPostProcessorConfig
@@ -65,31 +67,8 @@ type rawProvisionerConfig struct {
// way.
func ParseTemplate(data []byte) (t *Template, err error) {
var rawTplInterface interface{}
err = json.Unmarshal(data, &rawTplInterface)
err = jsonutil.Unmarshal(data, &rawTplInterface)
if err != nil {
syntaxErr, ok := err.(*json.SyntaxError)
if !ok {
return
}
// We have a syntax error. Extract out the line number and friends.
// https://groups.google.com/forum/#!topic/golang-nuts/fizimmXtVfc
newline := []byte{'\x0a'}
// Calculate the start/end position of the line where the error is
start := bytes.LastIndex(data[:syntaxErr.Offset], newline) + 1
end := len(data)
if idx := bytes.Index(data[start:], newline); idx >= 0 {
end = start + idx
}
// Count the line number we're on plus the offset in the line
line := bytes.Count(data[:start], newline) + 1
pos := int(syntaxErr.Offset) - start - 1
err = fmt.Errorf("Error in line %d, char %d: %s\n%s",
line, pos, syntaxErr, data[start:end])
return
}
@@ -123,11 +102,17 @@ func ParseTemplate(data []byte) (t *Template, err error) {
}
t = &Template{}
t.Variables = make(map[string]string)
t.Builders = make(map[string]rawBuilderConfig)
t.Hooks = rawTpl.Hooks
t.PostProcessors = make([][]rawPostProcessorConfig, len(rawTpl.PostProcessors))
t.Provisioners = make([]rawProvisionerConfig, len(rawTpl.Provisioners))
// Gather all the variables
for k, v := range rawTpl.Variables {
t.Variables[k] = v
}
// Gather all the builders
for i, v := range rawTpl.Builders {
var raw rawBuilderConfig
@@ -247,6 +232,17 @@ func ParseTemplate(data []byte) (t *Template, err error) {
return
}
// ParseTemplateFile takes the given template file and parses it into
// a single template.
func ParseTemplateFile(path string) (*Template, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return ParseTemplate(data)
}
func parsePostProvisioner(i int, rawV interface{}) (result []map[string]interface{}, errors []error) {
switch v := rawV.(type) {
case string:
@@ -412,6 +408,7 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
hooks: hooks,
postProcessors: postProcessors,
provisioners: provisioners,
variables: t.Variables,
}
return
+94
View File
@@ -2,11 +2,47 @@ package packer
import (
"cgl.tideland.biz/asserts"
"io/ioutil"
"reflect"
"sort"
"testing"
)
func testComponentFinder() *ComponentFinder {
builderFactory := func(n string) (Builder, error) { return testBuilder(), nil }
ppFactory := func(n string) (PostProcessor, error) { return new(TestPostProcessor), nil }
provFactory := func(n string) (Provisioner, error) { return new(TestProvisioner), nil }
return &ComponentFinder{
Builder: builderFactory,
PostProcessor: ppFactory,
Provisioner: provFactory,
}
}
func TestParseTemplateFile_basic(t *testing.T) {
data := `
{
"builders": [{"type": "something"}]
}
`
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Write([]byte(data))
tf.Close()
result, err := ParseTemplateFile(tf.Name())
if err != nil {
t.Fatalf("err: %s", err)
}
if len(result.Builders) != 1 {
t.Fatalf("bad: %#v", result.Builders)
}
}
func TestParseTemplate_Basic(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
@@ -300,6 +336,28 @@ func TestParseTemplate_Provisioners(t *testing.T) {
assert.NotNil(result.Provisioners[0].rawConfig, "should have raw config")
}
func TestParseTemplate_Variables(t *testing.T) {
data := `
{
"variables": {
"foo": "bar",
"bar": ""
},
"builders": [{"type": "something"}]
}
`
result, err := ParseTemplate([]byte(data))
if err != nil {
t.Fatalf("err: %s", err)
}
if result.Variables == nil || len(result.Variables) != 2 {
t.Fatalf("bad vars: %#v", result.Variables)
}
}
func TestTemplate_BuildNames(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
@@ -613,3 +671,39 @@ func TestTemplate_Build_ProvisionerOverride(t *testing.T) {
assert.Equal(len(coreBuild.provisioners), 1, "should have one provisioner")
assert.Equal(len(coreBuild.provisioners[0].config), 2, "should have two configs on the provisioner")
}
func TestTemplateBuild_variables(t *testing.T) {
data := `
{
"variables": {
"foo": "bar"
},
"builders": [
{
"name": "test1",
"type": "test-builder"
}
]
}
`
template, err := ParseTemplate([]byte(data))
if err != nil {
t.Fatalf("err: %s", err)
}
build, err := template.Build("test1", testComponentFinder())
if err != nil {
t.Fatalf("err: %s", err)
}
coreBuild, ok := build.(*coreBuild)
if !ok {
t.Fatalf("couldn't convert!")
}
if len(coreBuild.variables) != 1 {
t.Fatalf("bad vars: %#v", coreBuild.variables)
}
}
+94 -23
View File
@@ -11,6 +11,7 @@ import (
"runtime"
"strings"
"sync"
"time"
"unicode"
)
@@ -33,6 +34,7 @@ type Ui interface {
Say(string)
Message(string)
Error(string)
Machine(string, ...string)
}
// ColoredUi is a UI that is colored using terminal colors.
@@ -42,23 +44,32 @@ type ColoredUi struct {
Ui Ui
}
// PrefixedUi is a UI that wraps another UI implementation and adds a
// prefix to all the messages going out.
type PrefixedUi struct {
SayPrefix string
MessagePrefix string
Ui Ui
// TargettedUi is a UI that wraps another UI implementation and modifies
// the output to indicate a specific target. Specifically, all Say output
// is prefixed with the target name. Message output is not prefixed but
// is offset by the length of the target so that output is lined up properly
// with Say output. Machine-readable output has the proper target set.
type TargettedUi struct {
Target string
Ui Ui
}
// The ReaderWriterUi is a UI that writes and reads from standard Go
// io.Reader and io.Writer.
type ReaderWriterUi struct {
// The BasicUI is a UI that reads and writes from a standard Go reader
// and writer. It is safe to be called from multiple goroutines. Machine
// readable output is simply logged for this UI.
type BasicUi struct {
Reader io.Reader
Writer io.Writer
l sync.Mutex
interrupted bool
}
// MachineReadableUi is a UI that only outputs machine-readable output
// to the given Writer.
type MachineReadableUi struct {
Writer io.Writer
}
func (u *ColoredUi) Ask(query string) (string, error) {
return u.Ui.Ask(u.colorize(query, u.Color, true))
}
@@ -80,6 +91,11 @@ func (u *ColoredUi) Error(message string) {
u.Ui.Error(u.colorize(message, color, true))
}
func (u *ColoredUi) Machine(t string, args ...string) {
// Don't colorize machine-readable output
u.Ui.Machine(t, args...)
}
func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string {
if !u.supportsColors() {
return message
@@ -107,33 +123,43 @@ func (u *ColoredUi) supportsColors() bool {
return cygwin
}
func (u *PrefixedUi) Ask(query string) (string, error) {
return u.Ui.Ask(u.prefixLines(u.SayPrefix, query))
func (u *TargettedUi) Ask(query string) (string, error) {
return u.Ui.Ask(u.prefixLines(true, query))
}
func (u *PrefixedUi) Say(message string) {
u.Ui.Say(u.prefixLines(u.SayPrefix, message))
func (u *TargettedUi) Say(message string) {
u.Ui.Say(u.prefixLines(true, message))
}
func (u *PrefixedUi) Message(message string) {
u.Ui.Message(u.prefixLines(u.MessagePrefix, message))
func (u *TargettedUi) Message(message string) {
u.Ui.Message(u.prefixLines(false, message))
}
func (u *PrefixedUi) Error(message string) {
u.Ui.Error(u.prefixLines(u.SayPrefix, message))
func (u *TargettedUi) Error(message string) {
u.Ui.Error(u.prefixLines(true, message))
}
func (u *PrefixedUi) prefixLines(prefix, message string) string {
func (u *TargettedUi) Machine(t string, args ...string) {
// Prefix in the target, then pass through
u.Ui.Machine(fmt.Sprintf("%s,%s", u.Target, t), args...)
}
func (u *TargettedUi) prefixLines(arrow bool, message string) string {
arrowText := "==>"
if !arrow {
arrowText = strings.Repeat(" ", len(arrowText))
}
var result bytes.Buffer
for _, line := range strings.Split(message, "\n") {
result.WriteString(fmt.Sprintf("%s: %s\n", prefix, line))
result.WriteString(fmt.Sprintf("%s %s: %s\n", arrowText, u.Target, line))
}
return strings.TrimRightFunc(result.String(), unicode.IsSpace)
}
func (rw *ReaderWriterUi) Ask(query string) (string, error) {
func (rw *BasicUi) Ask(query string) (string, error) {
rw.l.Lock()
defer rw.l.Unlock()
@@ -177,7 +203,7 @@ func (rw *ReaderWriterUi) Ask(query string) (string, error) {
}
}
func (rw *ReaderWriterUi) Say(message string) {
func (rw *BasicUi) Say(message string) {
rw.l.Lock()
defer rw.l.Unlock()
@@ -188,7 +214,7 @@ func (rw *ReaderWriterUi) Say(message string) {
}
}
func (rw *ReaderWriterUi) Message(message string) {
func (rw *BasicUi) Message(message string) {
rw.l.Lock()
defer rw.l.Unlock()
@@ -199,7 +225,7 @@ func (rw *ReaderWriterUi) Message(message string) {
}
}
func (rw *ReaderWriterUi) Error(message string) {
func (rw *BasicUi) Error(message string) {
rw.l.Lock()
defer rw.l.Unlock()
@@ -209,3 +235,48 @@ func (rw *ReaderWriterUi) Error(message string) {
panic(err)
}
}
func (rw *BasicUi) Machine(t string, args ...string) {
log.Printf("machine readable: %s %#v", t, args)
}
func (u *MachineReadableUi) Ask(query string) (string, error) {
return "", errors.New("machine-readable UI can't ask")
}
func (u *MachineReadableUi) Say(message string) {
u.Machine("ui", "say", message)
}
func (u *MachineReadableUi) Message(message string) {
u.Machine("ui", "message", message)
}
func (u *MachineReadableUi) Error(message string) {
u.Machine("ui", "error", message)
}
func (u *MachineReadableUi) Machine(category string, args ...string) {
now := time.Now().UTC()
// Determine if we have a target, and set it
target := ""
commaIdx := strings.Index(category, ",")
if commaIdx > -1 {
target = category[0:commaIdx]
category = category[commaIdx+1:]
}
// Prepare the args
for i, v := range args {
args[i] = strings.Replace(v, ",", "%!(PACKER_COMMA)", -1)
args[i] = strings.Replace(args[i], "\r", "\\r", -1)
args[i] = strings.Replace(args[i], "\n", "\\n", -1)
}
argsString := strings.Join(args, ",")
_, err := fmt.Fprintf(u.Writer, "%d,%s,%s,%s\n", now.Unix(), target, category, argsString)
if err != nil {
panic(err)
}
}
+75 -21
View File
@@ -3,11 +3,12 @@ package packer
import (
"bytes"
"cgl.tideland.biz/asserts"
"strings"
"testing"
)
func testUi() *ReaderWriterUi {
return &ReaderWriterUi{
func testUi() *BasicUi {
return &BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
}
@@ -36,23 +37,26 @@ func TestColoredUi(t *testing.T) {
}
}
func TestPrefixedUi(t *testing.T) {
func TestTargettedUi(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
bufferUi := testUi()
prefixUi := &PrefixedUi{"mitchell", "bar", bufferUi}
targettedUi := &TargettedUi{
Target: "foo",
Ui: bufferUi,
}
prefixUi.Say("foo")
assert.Equal(readWriter(bufferUi), "mitchell: foo\n", "should have prefix")
targettedUi.Say("foo")
assert.Equal(readWriter(bufferUi), "==> foo: foo\n", "should have prefix")
prefixUi.Message("foo")
assert.Equal(readWriter(bufferUi), "bar: foo\n", "should have prefix")
targettedUi.Message("foo")
assert.Equal(readWriter(bufferUi), " foo: foo\n", "should have prefix")
prefixUi.Error("bar")
assert.Equal(readWriter(bufferUi), "mitchell: bar\n", "should have prefix")
targettedUi.Error("bar")
assert.Equal(readWriter(bufferUi), "==> foo: bar\n", "should have prefix")
prefixUi.Say("foo\nbar")
assert.Equal(readWriter(bufferUi), "mitchell: foo\nmitchell: bar\n", "should multiline")
targettedUi.Say("foo\nbar")
assert.Equal(readWriter(bufferUi), "==> foo: foo\n==> foo: bar\n", "should multiline")
}
func TestColoredUi_ImplUi(t *testing.T) {
@@ -63,23 +67,23 @@ func TestColoredUi_ImplUi(t *testing.T) {
}
}
func TestPrefixedUi_ImplUi(t *testing.T) {
func TestTargettedUi_ImplUi(t *testing.T) {
var raw interface{}
raw = &PrefixedUi{}
raw = &TargettedUi{}
if _, ok := raw.(Ui); !ok {
t.Fatalf("PrefixedUi must implement Ui")
t.Fatalf("TargettedUi must implement Ui")
}
}
func TestReaderWriterUi_ImplUi(t *testing.T) {
func TestBasicUi_ImplUi(t *testing.T) {
var raw interface{}
raw = &ReaderWriterUi{}
raw = &BasicUi{}
if _, ok := raw.(Ui); !ok {
t.Fatalf("ReaderWriterUi must implement Ui")
t.Fatalf("BasicUi must implement Ui")
}
}
func TestReaderWriterUi_Error(t *testing.T) {
func TestBasicUi_Error(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
bufferUi := testUi()
@@ -91,7 +95,7 @@ func TestReaderWriterUi_Error(t *testing.T) {
assert.Equal(readWriter(bufferUi), "5\n", "formatting")
}
func TestReaderWriterUi_Say(t *testing.T) {
func TestBasicUi_Say(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
bufferUi := testUi()
@@ -103,9 +107,59 @@ func TestReaderWriterUi_Say(t *testing.T) {
assert.Equal(readWriter(bufferUi), "5\n", "formatting")
}
func TestMachineReadableUi_ImplUi(t *testing.T) {
var raw interface{}
raw = &MachineReadableUi{}
if _, ok := raw.(Ui); !ok {
t.Fatalf("MachineReadableUi must implement Ui")
}
}
func TestMachineReadableUi(t *testing.T) {
var data, expected string
buf := new(bytes.Buffer)
ui := &MachineReadableUi{Writer: buf}
// No target
ui.Machine("foo", "bar", "baz")
data = strings.SplitN(buf.String(), ",", 2)[1]
expected = ",foo,bar,baz\n"
if data != expected {
t.Fatalf("bad: %s", data)
}
// Target
buf.Reset()
ui.Machine("mitchellh,foo", "bar", "baz")
data = strings.SplitN(buf.String(), ",", 2)[1]
expected = "mitchellh,foo,bar,baz\n"
if data != expected {
t.Fatalf("bad: %s", data)
}
// Commas
buf.Reset()
ui.Machine("foo", "foo,bar")
data = strings.SplitN(buf.String(), ",", 2)[1]
expected = ",foo,foo%!(PACKER_COMMA)bar\n"
if data != expected {
t.Fatalf("bad: %s", data)
}
// New lines
buf.Reset()
ui.Machine("foo", "foo\n")
data = strings.SplitN(buf.String(), ",", 2)[1]
expected = ",foo,foo\\n\n"
if data != expected {
t.Fatalf("bad: %#v", data)
}
}
// This reads the output from the bytes.Buffer in our test object
// and then resets the buffer.
func readWriter(ui *ReaderWriterUi) (result string) {
func readWriter(ui *BasicUi) (result string) {
buffer := ui.Writer.(*bytes.Buffer)
result = buffer.String()
buffer.Reset()
+5 -1
View File
@@ -10,7 +10,7 @@ import (
var GitCommit string
// The version of packer.
const Version = "0.2.3"
const Version = "0.3.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
@@ -27,6 +27,10 @@ command-line flags for this command.`
}
func (versionCommand) Run(env Environment, args []string) int {
env.Ui().Machine("version", Version)
env.Ui().Machine("version-prelease", VersionPrerelease)
env.Ui().Machine("version-commit", GitCommit)
var versionString bytes.Buffer
fmt.Fprintf(&versionString, "Packer v%s", Version)
if VersionPrerelease != "" {
+34
View File
@@ -1 +1,35 @@
package main
import (
"reflect"
"testing"
)
func TestExtractMachineReadable(t *testing.T) {
var args, expected, result []string
var mr bool
// Not
args = []string{"foo", "bar", "baz"}
result, mr = extractMachineReadable(args)
expected = []string{"foo", "bar", "baz"}
if !reflect.DeepEqual(result, expected) {
t.Fatalf("bad: %#v", result)
}
if mr {
t.Fatal("should not be mr")
}
// Yes
args = []string{"foo", "-machine-readable", "baz"}
result, mr = extractMachineReadable(args)
expected = []string{"foo", "baz"}
if !reflect.DeepEqual(result, expected) {
t.Fatalf("bad: %#v", result)
}
if !mr {
t.Fatal("should be mr")
}
}
+24
View File
@@ -9,11 +9,15 @@ import (
)
type config struct {
common.PackerConfig `mapstructure:",squash"`
// The local path of the file to upload.
Source string
// The remote path where the local file will be uploaded to.
Destination string
tpl *common.Template
}
type Provisioner struct {
@@ -26,9 +30,29 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
return err
}
p.config.tpl, err = common.NewTemplate()
if err != nil {
return err
}
p.config.tpl.UserVars = p.config.PackerUserVars
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
templates := map[string]*string{
"source": &p.config.Source,
"destination": &p.config.Destination,
}
for n, ptr := range templates {
var err error
*ptr, err = p.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
if _, err := os.Stat(p.config.Source); err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Bad source '%s': %s", p.config.Source, err))
+3
View File
@@ -106,6 +106,9 @@ func (su *stubUi) Ask(string) (string, error) {
func (su *stubUi) Error(string) {
}
func (su *stubUi) Machine(string, ...string) {
}
func (su *stubUi) Message(string) {
}
+45 -15
View File
@@ -15,6 +15,8 @@ import (
const DefaultTempConfigDir = "/tmp/salt"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
// If true, run the salt-bootstrap script
SkipBootstrap bool `mapstructure:"skip_bootstrap"`
BootstrapArgs string `mapstructure:"bootstrap_args"`
@@ -24,6 +26,8 @@ type Config struct {
// Where files will be copied before moving to the /srv/salt directory
TempConfigDir string `mapstructure:"temp_config_dir"`
tpl *common.Template
}
type Provisioner struct {
@@ -36,6 +40,12 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
return err
}
p.config.tpl, err = common.NewTemplate()
if err != nil {
return err
}
p.config.tpl.UserVars = p.config.PackerUserVars
if p.config.TempConfigDir == "" {
p.config.TempConfigDir = DefaultTempConfigDir
}
@@ -43,12 +53,26 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
if p.config.LocalStateTree == "" {
errs = packer.MultiErrorAppend(errs,
errors.New("Please specify a local_state_tree"))
} else if _, err := os.Stat(p.config.LocalStateTree); err != nil {
errs = packer.MultiErrorAppend(errs,
errors.New("local_state_tree must exist and be accessible"))
templates := map[string]*string{
"bootstrap_args": &p.config.BootstrapArgs,
"local_state_tree": &p.config.LocalStateTree,
"temp_config_dir": &p.config.TempConfigDir,
}
for n, ptr := range templates {
var err error
*ptr, err = p.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
if p.config.LocalStateTree != "" {
if _, err := os.Stat(p.config.LocalStateTree); err != nil {
errs = packer.MultiErrorAppend(errs,
errors.New("local_state_tree must exist and be accessible"))
}
}
if errs != nil && len(errs.Errors) > 0 {
@@ -74,7 +98,11 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Message(fmt.Sprintf("Creating remote directory: %s", p.config.TempConfigDir))
cmd := &packer.RemoteCmd{Command: fmt.Sprintf("mkdir -p %s", p.config.TempConfigDir)}
if err = cmd.StartWithUi(comm, ui); err != nil {
if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
if err == nil {
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
}
return fmt.Errorf("Error creating remote salt state directory: %s", err)
}
@@ -85,20 +113,22 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Message(fmt.Sprintf("Moving %s to /srv/salt", p.config.TempConfigDir))
cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s /srv/salt", p.config.TempConfigDir)}
if err = cmd.StartWithUi(comm, ui); err != nil {
if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
if err == nil {
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
}
return fmt.Errorf("Unable to move %s to /srv/salt: %d", p.config.TempConfigDir, err)
}
ui.Message("Running highstate")
cmd = &packer.RemoteCmd{Command: "sudo salt-call --local state.highstate -l info"}
if err = cmd.StartWithUi(comm, ui); err != nil {
return fmt.Errorf("Error executing highstate: %s", err)
}
if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
if err == nil {
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
}
ui.Message("Removing /srv/salt")
cmd = &packer.RemoteCmd{Command: "sudo rm -r /srv/salt"}
if err = cmd.StartWithUi(comm, ui); err != nil {
return fmt.Errorf("Unable to remove /srv/salt: %d", err)
return fmt.Errorf("Error executing highstate: %s", err)
}
return nil
+49 -9
View File
@@ -4,7 +4,6 @@ package shell
import (
"bufio"
"bytes"
"errors"
"fmt"
"github.com/mitchellh/packer/common"
@@ -13,12 +12,13 @@ import (
"log"
"os"
"strings"
"text/template"
)
const DefaultRemotePath = "/tmp/script.sh"
type config struct {
common.PackerConfig `mapstructure:",squash"`
// An inline script to execute. Multiple strings are all executed
// in the context of a single shell.
Inline []string
@@ -45,9 +45,7 @@ type config struct {
// can be used to inject the environment_vars into the environment.
ExecuteCommand string `mapstructure:"execute_command"`
// Packer configurations, these come from Packer itself
PackerBuildName string `mapstructure:"packer_build_name"`
PackerBuilderType string `mapstructure:"packer_builder_type"`
tpl *common.Template
}
type Provisioner struct {
@@ -65,6 +63,12 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
return err
}
p.config.tpl, err = common.NewTemplate()
if err != nil {
return err
}
p.config.tpl.UserVars = p.config.PackerUserVars
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
@@ -101,6 +105,38 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
p.config.Scripts = []string{p.config.Script}
}
templates := map[string]*string{
"inline_shebang": &p.config.InlineShebang,
"script": &p.config.Script,
"remote_path": &p.config.RemotePath,
}
for n, ptr := range templates {
var err error
*ptr, err = p.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
sliceTemplates := map[string][]string{
"inline": p.config.Inline,
"scripts": p.config.Scripts,
"environment_vars": p.config.Vars,
}
for n, slice := range sliceTemplates {
for i, elem := range slice {
var err error
slice[i], err = p.config.tpl.Process(elem, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err))
}
}
}
if len(p.config.Scripts) == 0 && p.config.Inline == nil {
errs = packer.MultiErrorAppend(errs,
errors.New("Either a script file or inline script must be specified."))
@@ -193,11 +229,15 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
flattendVars := strings.Join(envVars, " ")
// Compile the command
var command bytes.Buffer
t := template.Must(template.New("command").Parse(p.config.ExecuteCommand))
t.Execute(&command, &ExecuteCommandTemplate{flattendVars, p.config.RemotePath})
command, err := p.config.tpl.Process(p.config.ExecuteCommand, &ExecuteCommandTemplate{
Vars: flattendVars,
Path: p.config.RemotePath,
})
if err != nil {
return fmt.Errorf("Error processing command: %s", err)
}
cmd := &packer.RemoteCmd{Command: command.String()}
cmd := &packer.RemoteCmd{Command: command}
log.Printf("Executing command: %s", cmd.Command)
if err := cmd.StartWithUi(comm, ui); err != nil {
return fmt.Errorf("Failed executing command: %s", err)
+1 -1
View File
@@ -17,7 +17,7 @@ DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
cd $DIR
# Get the git commit
GIT_COMMIT=$(git rev-parse --short HEAD)
GIT_COMMIT=$(git rev-parse HEAD)
GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
# Compile the main Packer app
@@ -48,5 +48,18 @@ list as contributors come and go.
</div>
</div>
<div class="person">
<img class="pull-left" src="http://www.gravatar.com/avatar/2f7fc9cb7558e3ea48f5a86fa90a78da.png?s=125">
<div class="bio">
<h3>Mark Peek (<a href="https://github.com/markpeek">@markpeek</a>)</h3>
<p>
In addition to Packer, Mark Peek helps maintain
various open source projects such as
<a href="https://github.com/cloudtools">cloudtools</a> and
<a href="https://github.com/ironport">IronPort Python libraries</a>.
Mark is also a <a href="https://FreeBSD.org">FreeBSD committer</a>.</p>
</div>
</div>
<div class="clearfix"></div>
</div>
@@ -62,8 +62,8 @@ Required:
* `ami_name` (string) - The name of the resulting AMI that will appear
when managing AMIs in the AWS console or via APIs. This must be unique.
To help make this unique, certain template parameters are available for
this value, which are documented below.
To help make this unique, use a function like `timestamp` (see
[configuration templates](/docs/templates/configuration-templates.html) for more info)
* `secret_key` (string) - The secret key used to communicate with AWS.
If not specified, Packer will attempt to read this from environmental
@@ -78,6 +78,21 @@ Required:
Optional:
* `ami_description` (string) - The description to set for the resulting
AMI(s). By default this description is empty.
* `ami_groups` (array of string) - A list of groups that have access
to launch the resulting AMI(s). By default no groups have permission
to launch the AMI.
* `ami_product_codes` (array of string) - A list of product codes to
associate with the AMI. By default no product codes are associated with
the AMI.
* `ami_users` (array of string) - A list of account IDs that have access
to launch the resulting AMI(s). By default no additional users other than the user
creating the AMI has permissions to launch it.
* `chroot_mounts` (list of list of strings) - This is a list of additional
devices to mount into the chroot environment. This configuration parameter
requires some additional documentation which is in the "Chroot Mounts" section
@@ -115,22 +130,10 @@ Here is a basic example. It is completely valid except for the access keys:
"access_key": "YOUR KEY HERE",
"secret_key": "YOUR SECRET KEY HERE",
"source_ami": "ami-e81d5881",
"ami_name": "packer-amazon-chroot {{.CreateTime}}"
"ami_name": "packer-amazon-chroot {{timestamp}}"
}
</pre>
## AMI Name Variables
The AMI name specified by the `ami_name` configuration variable is actually
treated as a [configuration template](/docs/templates/configuration-templates.html).
Packer provides a set of variables that it will replace
within the AMI name. This helps ensure the AMI name is unique, as AWS requires.
The available variables are shown below:
* `CreateTime` - This will be replaced with the Unix timestamp of when
the AMI was built.
## Chroot Mounts
The `chroot_mounts` configuration can be used to mount additional devices
@@ -37,8 +37,8 @@ Required:
* `ami_name` (string) - The name of the resulting AMI that will appear
when managing AMIs in the AWS console or via APIs. This must be unique.
To help make this unique, certain template parameters are available for
this value, which are documented below.
To help make this unique, use a function like `timestamp` (see
[configuration templates](/docs/templates/configuration-templates.html) for more info)
* `instance_type` (string) - The EC2 instance type to use while building
the AMI, such as "m1.small".
@@ -58,6 +58,21 @@ Required:
Optional:
* `ami_description` (string) - The description to set for the resulting
AMI(s). By default this description is empty.
* `ami_groups` (array of string) - A list of groups that have access
to launch the resulting AMI(s). By default no groups have permission
to launch the AMI.
* `ami_product_codes` (array of string) - A list of product codes to
associate with the AMI. By default no product codes are associated with
the AMI.
* `ami_users` (array of string) - A list of account IDs that have access
to launch the resulting AMI(s). By default no additional users other than the user
creating the AMI has permissions to launch it.
* `iam_instance_profile` (string) - The name of an
[IAM instance profile](http://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html)
to launch the EC2 instance with.
@@ -80,6 +95,14 @@ Optional:
* `tags` (object of key/value strings) - Tags applied to the AMI.
* `user_data` (string) - User data to apply when launching the instance.
Note that you need to be careful about escaping characters due to the
templates being JSON. It is often more convenient to use `user_data_file`,
instead.
* `user_data_file` (string) - Path to a file that will be used for the
user data when launching the instance.
* `vpc_id` (string) - If launching into a VPC subnet, Packer needs the
VPC ID in order to create a temporary security group within the VPC.
@@ -96,7 +119,7 @@ Here is a basic example. It is completely valid except for the access keys:
"source_ami": "ami-de0d9eb7",
"instance_type": "t1.micro",
"ssh_username": "ubuntu",
"ami_name": "packer-quick-start {{.CreateTime}}",
"ami_name": "packer-quick-start {{timestamp}}",
}
</pre>
@@ -121,22 +144,10 @@ Here is an example using the optional AMI tags. This will add the tags
"source_ami": "ami-de0d9eb7",
"instance_type": "t1.micro",
"ssh_username": "ubuntu",
"ami_name": "packer-quick-start {{.CreateTime}}",
"ami_name": "packer-quick-start {{timestamp}}",
"tags": {
"OS_Version": "Ubuntu",
"Release": "Latest"
}
}
</pre>
## AMI Name Variables
The AMI name specified by the `ami_name` configuration variable is actually
treated as a [configuration template](/docs/templates/configuration-templates.html).
Packer provides a set of variables that it will replace
within the AMI name. This helps ensure the AMI name is unique, as AWS requires.
The available variables are shown below:
* `CreateTime` - This will be replaced with the Unix timestamp of when
the AMI was built.
@@ -41,8 +41,8 @@ Required:
* `ami_name` (string) - The name of the resulting AMI that will appear
when managing AMIs in the AWS console or via APIs. This must be unique.
To help make this unique, certain template parameters are available for
this value, which are documented below.
To help make this unique, use a function like `timestamp` (see
[configuration templates](/docs/templates/configuration-templates.html) for more info)
* `instance_type` (string) - The EC2 instance type to use while building
the AMI, such as "m1.small".
@@ -73,12 +73,27 @@ Required:
Optional:
* `ami_description` (string) - The description to set for the resulting
AMI(s). By default this description is empty.
* `ami_groups` (array of string) - A list of groups that have access
to launch the resulting AMI(s). By default no groups have permission
to launch the AMI.
* `ami_product_codes` (array of string) - A list of product codes to
associate with the AMI. By default no product codes are associated with
the AMI.
* `ami_users` (array of string) - A list of account IDs that have access
to launch the resulting AMI(s). By default no additional users other than the user
creating the AMI has permissions to launch it.
* `bundle_destination` (string) - The directory on the running instance
where the bundled AMI will be saved prior to uploading. By default this is
"/tmp". This directory must exist and be writable.
* `bundle_prefix` (string) - The prefix for files created from bundling
the root volume. By default this is "image-{{.Createtime}}". The `CreateTime`
the root volume. By default this is "image-{{timestamp}}". The `timestamp`
variable should be used to make sure this is unique, otherwise it can
collide with other created AMIs by Packer in your account.
@@ -111,6 +126,14 @@ Optional:
* `tags` (object of key/value strings) - Tags applied to the AMI.
* `user_data` (string) - User data to apply when launching the instance.
Note that you need to be careful about escaping characters due to the
templates being JSON. It is often more convenient to use `user_data_file`,
instead.
* `user_data_file` (string) - Path to a file that will be used for the
user data when launching the instance.
* `vpc_id` (string) - If launching into a VPC subnet, Packer needs the
VPC ID in order to create a temporary security group within the VPC.
@@ -140,7 +163,7 @@ Here is a basic example. It is completely valid except for the access keys:
"x509_key_path": "x509.key",
"x509_upload_path": "/tmp",
"ami_name": "packer-quick-start {{.CreateTime}}"
"ami_name": "packer-quick-start {{timestamp}}"
}
</pre>
@@ -151,18 +174,6 @@ the section above for more information on what environmental variables Packer
will look for.
</div>
## AMI Name Variables
The AMI name specified by the `ami_name` configuration variable is actually
treated as a [configuration template](/docs/templates/configuration-templates.html).
Packer provides a set of variables that it will replace
within the AMI name. This helps ensure the AMI name is unique, as AWS requires.
The available variables are shown below:
* `CreateTime` - This will be replaced with the Unix timestamp of when
the AMI was built.
## Custom Bundle Commands
A lot of the process required for creating an instance-store backed AMI
@@ -202,6 +213,14 @@ sudo -n ec2-bundle-vol \
The available template variables should be self-explanatory based on the
parameters they're used to satisfy the `ec2-bundle-vol` command.
<div class="alert alert-block">
<strong>Warning!</strong> Some versions of ec2-bundle-vol silently
ignore all .pem and .gpg files during the bundling of the AMI, which can
cause problems on some systems, such as Ubuntu. You may want to
customize the bundle volume command to include those files (see the
<code>--no-filter</code> option of ec2-bundle-vol).
</div>
### Bundle Upload Command
The default value for `bundle_upload_command` is shown below. It is split
@@ -52,9 +52,9 @@ Optional:
which is the 512MB droplet.
* `snapshot_name` (string) - The name of the resulting snapshot that will
appear in your account. This must be unique. To help make this unique,
certain template parameters are available for this value, and are documented
below.
appear in your account. This must be unique.
To help make this unique, use a function like `timestamp` (see
[configuration templates](/docs/templates/configuration-templates.html) for more info)
* `ssh_port` (int) - The port that SSH will be available on. Defaults to port
22.
@@ -83,17 +83,6 @@ own access tokens:
}
</pre>
## Snapshot Name Variables
The `snapshot_name` for a DigitalOcean image must be unique. The configuration
variable is actually treated as a [configuration template](/docs/templates/configuration-templates.html) and has various variables available to help keep this unique. Read
the prior linked page for information on syntax if you're unfamiliar with it.
The available variables are shown below:
* `CreateTime` - This will be replaced with the Unix timestamp of when the
image is created.
## Finding Image, Region, and Size IDs
Unfortunately, finding a list of available values for `image_id`, `region_id`,
@@ -1,5 +1,6 @@
---
layout: "docs"
page_title: "Build - Command-Line"
---
# Command-Line: Build
@@ -22,7 +23,7 @@ artifacts that are created will be outputted at the end of the build.
In general, a builder supporting the forced build will remove the artifacts from
the previous build. This will allow the user to repeat a build without having to
manually clean these artifacts beforehand.
* `-except=foo,bar,baz` - Builds all the builds except those with the given
comma-separated names. Build names by default are the names of their builders,
unless a specific `name` attribute is specified within the configuration.
@@ -1,6 +1,6 @@
---
layout: "docs"
page_title: "Command-line: Fix"
page_title: "Fix - Command-Line"
---
# Command-Line: Fix
@@ -0,0 +1,83 @@
---
layout: "docs"
page_title: "Machine-Readable Output - Command-Line"
---
# Machine-Readable Output
By default, the output of Packer is very human-readable. It uses nice
formatting, spacing, and colors in order to make Packer a pleasure to use.
However, Packer was built with automation in mind. To that end, Packer
supports a fully machine-readable output setting, allowing you to use
Packer in automated environments.
The machine-readable output format is easy to use and read and was made
with Unix tools in mind, so it is awk/sed/grep/etc. friendly.
## Enabling
The machine-readable output format can be enabled by passing the
`-machine-readable` flag to any Packer command. This immediately enables
all output to become machine-readable on stdout. Logging, if enabled,
continues to appear on stderr. An example of the output is shown
below:
```
$ packer -machine-readable version
1376289459,,version,0.2.4
1376289459,,version-prerelease,
1376289459,,version-commit,eed6ece
1376289459,,ui,say,Packer v0.2.4.dev (eed6ece+CHANGES)
```
The format will be covered in more detail later. But as you can see,
the output immediately becomes machine-friendly. Try some other commands
with the `-machine-readable` flag to see!
## Format
The machine readable format is a line-oriented, comma-delimeted text
format. This makes it extremely to parse using standard Unix tools such
as awk or grep in addition to full programming languages like Ruby or
Python.
The format is:
```
timestamp,target,type,data...
```
Each component is explained below:
* **timestamp** is a Unix timestamp in UTC of when the message was
printed.
* **target** is the target of the following output. This is empty if
the message is related to Packer globally. Otherwise, this is generally
a build name so you can relate output to a specific build while parallel
builds are running.
* **type** is the type of machine-readable message being outputted. There
are a set of standard types which are covered later, but each component
of Packer (builders, provisioners, etc.) may output their own custom types
as well, allowing the machine-readable output to be infinitely flexible.
* **data** is zero or more comma-seperated values associated with the prior
type. The exact amount and meaning of this data is type-dependent, so you
must read the documentation associated with the type to understand fully.
Within the format, if data contains a comma, it is replaced with
`%!(PACKER_COMMA)`. This was preferred over an escape character such as
`\'` because it is more friendly to tools like awk.
Newlines within the format are replaced with their respective standard
escape sequence. Newlines become a literal `\n` within the output. Carriage
returns become a literal `\r`.
## Message Types
The set of machine-readable message types can be found in the
[machine-readable format](/docs/machine-readable/index.html)
complete documentation section. This section contains documentation
on all the message types exposed by Packer core as well as all the
components that ship with Packer by default.
@@ -1,5 +1,6 @@
---
layout: "docs"
page_title: "Validate - Command-Line"
---
# Command-Line: Validate
@@ -0,0 +1,125 @@
---
layout: "docs_machine_readable"
page_title: "Command: build - Machine-Readable Reference"
---
# Build Command Types
These are the machine-readable types that exist as part of the output
of `packer build`.
<dl>
<dt>artifact (>= 2)</dt>
<dd>
<p>
Information about an artifact of the targetted item. This is a
fairly complex (but uniform!) machine-readable type that contains
subtypes. The subtypes are documented within this page in the
syntax of "artifact subtype: SUBTYPE". The number of arguments within
that subtype is in addition to the artifact args.
</p>
<p>
<strong>Data 1: index</strong> - The zero-based index of the
artifact being described. This goes up to "artifact-count" (see
below).
</p>
<p>
<strong>Data 2: subtype</strong> - The subtype that describes
the remaining arguments. See the documentation for the
subtype docs throughout this page.
</p>
<p>
<strong>Data 3..n: subtype data</strong> - Zero or more additional
data points related to the subtype. The exact count and meaning
of this subtypes comes from the subtype documentation.
</p>
</dd>
<dt>artifact-count (1)</dt>
<dd>
<p>
The number of artifacts associated with the given target.
</p>
<p>
<strong>Data 1: count</strong> - The number of artifacts as
a base 10 integer.
</p>
</dd>
<dt>artifact subtype: builder-id (1)</dt>
<dd>
<p>
The unique ID of the builder that created this artifact.
</p>
<p>
<strong>Data 1: id</strong> - The unique ID of the builder.
</p>
</dd>
<dt>artifact subtype: file (2)</dt>
<dd>
<p>
A single file associated with the artifact. There are 0 to
"files-count" of these entries to describe every file that is
part of the artifact.
</p>
<p>
<strong>Data 1: index</strong> - Zero-based index of the file.
This goes from 0 to "files-count" minus one.
</p>
<p>
<strong>Data 2: filename</strong> - The filename.
</p>
</dd>
<dt>artifact subtype: files-count (1)</dt>
<dd>
<p>
The number of files associated with this artifact. Not all
artifacts have files associated with it.
</p>
<p>
<strong>Data 1: count</strong> - The number of files.
</p>
</dd>
<dt>artifact subtype: id (1)</dt>
<dd>
<p>
The ID (if any) of the artifact that was built. Not all artifacts
have associated IDs. For example, AMIs built have IDs associated
with them, but VirtualBox images do not. The exact format of the ID
is specific to the builder.
</p>
<p>
<strong>Data 1: id</strong> - The ID of the artifact.
</p>
</dd>
<dt>artifact subtype: nil (0)</dt>
<dd>
<p>
If present, this means that the artifact was nil, or that the targetted
build completed successfully but no artifact was created.
</p>
</dd>
<dt>artifact subtype: string (1)</dt>
<dd>
<p>
The human-readable string description of the artifact provided by
the artifact itself.
</p>
<p>
<strong>Data 1: string</strong> - The string output for the artifact.
</p>
</dd>
</dl>
@@ -0,0 +1,45 @@
---
layout: "docs_machine_readable"
page_title: "Command: version - Machine-Readable Reference"
---
# Version Command Types
These are the machine-readable types that exist as part of the output
of `packer version`.
<dl>
<dt>version (1)</dt>
<dd>
<p>The version number of Packer running.</p>
<p>
<strong>Data 1: version</strong> - The version of Packer running,
only including the major, minor, and patch versions. Example:
"0.2.4".
</p>
</dd>
<dt>version-commit (1)</dt>
<dd>
<p>The SHA1 of the Git commit that built this version of Packer.</p>
<p>
<strong>Data 1: commit SHA1</strong> - The SHA1 of the commit.
</p>
</dd>
<dt>version-prerelease (1)</dt>
<dd>
<p>
The prerelease tag (if any) for the running version of Packer. This
can be "beta", "dev", "alpha", etc. If this is empty, you can assume
it is a release version running.
</p>
<p>
<strong>Data 1: prerelease name</strong> - The name of the
prerelease tag.
</p>
</dd>
</dl>
@@ -0,0 +1,29 @@
---
layout: "docs_machine_readable"
page_title: "General Types - Machine-Readable Reference"
---
# General Types
These are the machine-readable types that can appear in almost any
machine-readable output and are provided by Packer core itself.
<dl>
<dt>ui (2)</dt>
<dd>
<p>
Specifies the output and type of output that would've normally
gone to the console if Packer wasn't running in human-readable
mode.
</p>
<p>
<strong>Data 1: type</strong> - The type of UI message that would've
been outputted. Can be "say", "message", or "error".
</p>
<p>
<strong>Data 2: output</strong> - The UI message that would have
been outputted.
</p>
</dd>
</dl>
@@ -0,0 +1,30 @@
---
layout: "docs_machine_readable"
page_title: "Machine-Readable Reference"
---
# Machine-Readable Reference
This is the reference for the various message categories for Packer
[machine-readable output](/docs/command-line/machine-readable.html).
Please read that page if you're unfamiliar with the general format and
usage for the machine-readable output.
The layout of this reference is split into where the types come from.
There are a set of core types that are from Packer core itself. Then
there are types that come from various components of Packer such as the
builders, provisioners, and more.
Within each section, the format of the documentation is the following:
<br>
<dl>
<dt>type-name (data-count)</dt>
<dd>
<p>Description of the type.</p>
<p>
<strong>Data 1: name</strong> - Description.
</p>
</dd>
</dl>
@@ -6,7 +6,8 @@ layout: "docs"
Type: `salt-masterless`
The salt-masterless provisioner provisions machines built by Packer using [Salt](http://saltstack.com/) states.
The `salt-masterless` provisioner provisions machines built by Packer using
[Salt](http://saltstack.com/) states, without connecting to a Salt master.
## Basic Example
@@ -15,7 +16,6 @@ The example below is fully functional.
<pre class="prettyprint">
{
"type": "salt-masterless",
"bootstrap_args": "git v0.16.0"
"local_state_tree": "/Users/me/salt"
}
</pre>
@@ -24,15 +24,21 @@ The example below is fully functional.
The reference of available configuration options is listed below. The only required argument is the path to your local salt state tree.
Required:
* `local_state_tree` (string) - The path to your local [state tree](http://docs.saltstack.com/ref/states/highstate.html#the-salt-state-tree). This will be uploaded to the `/srv/salt` on the remote, and removed before shutdown.
Optional:
* `skip_bootstrap` (boolean) - By default the salt provisioner runs [salt bootstrap](https://github.com/saltstack/salt-bootstrap) to install salt. Set this to true to skip this step.
* `local_state_tree` (string) - The path to your local
[state tree](http://docs.saltstack.com/ref/states/highstate.html#the-salt-state-tree).
This will be uploaded to the `/srv/salt` on the remote.
* `boostrap_args` (string) -
Arguments to send to the bootstrap script. Usage is somewhat documented on [github](https://github.com/saltstack/salt-bootstrap), but the [script itself](https://github.com/saltstack/salt-bootstrap/blob/develop/bootstrap-salt.sh) has more detailed usage instructions. By default, no arguments are sent to the script.
* `skip_bootstrap` (boolean) - By default the salt provisioner runs
[salt bootstrap](https://github.com/saltstack/salt-bootstrap) to install
salt. Set this to true to skip this step.
* `temp_config_dir` (string) - Where your local state tree will be copied before moving to the `/srv/salt` directory. Default is `/tmp/salt`.
* `boostrap_args` (string) - Arguments to send to the bootstrap script. Usage
is somewhat documented on [github](https://github.com/saltstack/salt-bootstrap),
but the [script itself](https://github.com/saltstack/salt-bootstrap/blob/develop/bootstrap-salt.sh)
has more detailed usage instructions. By default, no arguments are sent to
the script.
* `temp_config_dir` (string) - Where your local state tree will be copied
before moving to the `/srv/salt` directory. Default is `/tmp/salt`.
@@ -1,47 +0,0 @@
---
layout: "docs"
---
# Salt Masterless Provisioner
Type: `salt-masterless`
The `salt-masterless` provisioner provisions machines built by Packer using
[Salt](http://saltstack.com/) states, without connecting to a Salt master.
## Basic Example
The example below is fully functional.
<pre class="prettyprint">
{
"type": "salt-masterless",
"local_state_tree": "/Users/me/salt"
}
</pre>
## Configuration Reference
The reference of available configuration options is listed below. The only required argument is the path to your local salt state tree.
Required:
* `local_state_tree` (string) - The path to your local
[state tree](http://docs.saltstack.com/ref/states/highstate.html#the-salt-state-tree).
This will be uploaded to the `/srv/salt` on the remote, and removed before
shutdown.
Optional:
* `skip_bootstrap` (boolean) - By default the salt provisioner runs
[salt bootstrap](https://github.com/saltstack/salt-bootstrap) to install
salt. Set this to true to skip this step.
* `boostrap_args` (string) - Arguments to send to the bootstrap script. Usage
is somewhat documented on [github](https://github.com/saltstack/salt-bootstrap),
but the [script itself](https://github.com/saltstack/salt-bootstrap/blob/develop/bootstrap-salt.sh)
has more detailed usage instructions. By default, no arguments are sent to
the script.
* `temp_config_dir` (string) - Where your local state tree will be copied
before moving to the `/srv/salt` directory. Default is `/tmp/salt`.
@@ -1,37 +1,36 @@
---
layout: "docs"
page_title: "Configuration Templates"
---
# Configuration Templates
Certain configuration parameters within templates are themselves a
type of "template." These are not Packer templates, but text templates,
where variables can be used to modify the value of a configuration parameter
during runtime.
All strings within templates are processed by a common Packer templating
engine, where variables and functions can be used to modify the value of
a configuration parameter at runtime.
For example, the `ami_name` configuration for the [AMI builder](/docs/builders/amazon-ebs.html)
is a template. An example value may be `My Packer AMI {{.CreateTime}}`. At
runtime, this will be turned into `My Packer AMI 1370900368`, where the
"CreateTime" variable was replaced with the Unix timestamp of when the
AMI was actually created.
For example, the `{{timestamp}}` function can be used in any string to
generate the current timestamp. This is useful for configurations that require
unique keys, such as AMI names. By setting the AMI name to something like
`My Packer AMI {{timestamp}}`, the AMI name will be unique down to the second.
This sort of templating is pervasive throughout Packer. Instead of documenting
the templating syntax in each location, it is documented once here so
you know how to use it.
In addition to globally available functions like timestamp shown before,
some configurations have special local variables that are available only
for that configuration. These are recognizable because they're prefixed by
a period, such as `{{.Name}}`.
<div class="alert alert-info">
<strong>For advanced users:</strong> The templates are actually parsed and executed
using Go's <a href="http://golang.org/pkg/text/template/">text/template</a>
package. It therefore supports the complete template syntax.
</div>
The complete syntax is covered in the next section, followed by a reference
of globally available functions.
## Syntax
99% of the time all you'll need within configuration templates are variables.
Variables are accessed by using `{{.VARIABLENAME}}`. The "." prefixing the
variable name signals that you're accessing a variable on the root
template object. All template directives go between braces. Here is a piece
of a VMware VMX template that uses variables:
The syntax of templates is extremely simple. Anything template related
happens within double-braces: `{{ }}`. Variables are prefixed with a period
and capitalized, such as `{{.Variable}}` and functions are just directly
within the braces, such as `{{timestamp}}`.
Here is an example from the VMware VMX template that shows configuration
templates in action:
<pre>
.encoding = "UTF-8"
@@ -47,3 +46,11 @@ resulting in a VMX that looks like this:
displayName = "packer"
guestOS = "otherlinux"
</pre>
## Global Functions
While some configuration settings have local variables specific to only that
configuration, a set of functions are available globally for use in _any string_
in Packer templates. These are listed below for reference.
* ``timestamp`` - The current Unix timestamp in UTC.
+2 -2
View File
@@ -33,7 +33,7 @@ Along with each key, it is noted whether it is required or not.
information on how to define and configure a provisioner, read the
sub-section on [configuring provisioners in templates](/docs/templates/provisioners.html).
* `post-processors` (optional) is an array of one or more objects that defines the
* `post-processors` (optional) is an array of one or more objects that defines the
various post-processing steps to take with the built images. This is an optional
field. If not specified, then no post-processing will be done. For more
information on what post-processors do and how they're defined, read the
@@ -56,7 +56,7 @@ missing valid AWS access keys. Otherwise, it would work properly with
"source_ami": "ami-de0d9eb7",
"instance_type": "t1.micro",
"ssh_username": "ubuntu",
"ami_name": "packer {{.CreateTime}}"
"ami_name": "packer {{timestamp}}"
}
],
@@ -0,0 +1,115 @@
---
layout: "docs"
page_title: "User Variables in Templates"
---
# User Variables
User variables allow your templates to be further configured with variables
from the command-line, environmental variables, or files. This lets you
parameterize your templates so that you can keep secret tokens,
environment-specific data, and other types of information out of your
templates. This maximizes the portablility and shareability of the template.
Using user variables expects you know how
[configuration templates](/docs/templates/configuration-templates.html) work.
If you don't know how configuration templates work yet, please read that
page first.
## Usage
User variables must first be defined in a `variables` section within your
template. Even if you want a variable to default to an empty string, it
must be defined. This explicitivity makes it easy for newcomers to your
template to understand what can be modified using variables in your template.
The `variables` section is a simple key/value mapping of the variable
name to a default value. A default value can be the empty string. An
example is shown below:
<pre class="prettyprint">
{
"variables": {
"aws_access_key": "",
"aws_secret_key": ""
},
"builders": [{
"type": "amazon-ebs",
"access_key": "{{user `aws_access_key`}}",
"secret_key": "{{user `aws_secret_key`}}",
...
}]
}
</pre>
In the above example, the template defines two variables: `aws_access_key` and
`aws_secret_key`. They default to empty values.
Later, the variables are used within the builder we defined in order to
configure the actual keys for the Amazon builder.
Using the variables is extremely easy. Variables are used by calling
the user function in the form of <code>{{user &#96;variable&#96;}}</code>.
This function can be used in _any string_ within the template, in
builders, provisioners, _anything_. The user variable is available globally
within the template.
## Setting Variables
Now that we covered how to define and use variables within a template,
the next important point is how to actually set these variables. Packer
exposes two methods for setting variables: from the command line or
from a file.
### From the Command Line
To set variables from the command line, the `-var` flag is used as
a parameter to `packer build` (and some other commands). Continuing our example
above, we could build our template using the command below. The command
is split across multiple lines for readability, but can of course be a single
line.
```
$ packer build \
-var 'aws_access_key=foo' \
-var 'aws_secret_key=bar' \
template.json
```
As you can see, the `-var` flag can be specified multiple times in order
to set multiple variables. Also, variables set later on the command-line
override earlier set variables if it has already been set.
Finally, variables set from the command-line override all other methods
of setting variables. So if you specify a variable in a file (the next
method shown), you can override it using the command-line.
### From a File
Variables can also be set from an external JSON file. The `-var-file`
flag reads a file containing a basic key/value mapping of variables to
values and sets those variables. The JSON file is simple:
<pre class="prettyprint">
{
"aws_access_key": "foo",
"aws_secret_key": "bar"
}
</pre>
It is a single JSON object where the keys are variables and the values are
the variable values. Assuming this file is in `variables.json`, we can
build our template using the following command:
```
$ packer build -var-file=variables.json template.json
```
The `-var-file` flag can be specified multiple times and variables from
multiple files will be read and applied. As you'd expect, variables read
from files specified later override a variable set earlier if it has
already been set.
And as mentioned above, no matter where a `-var-file` is specified, a
`-var` flag on the command line will always override any variables from
a file.
@@ -54,7 +54,7 @@ briefly. Create a file `example.json` and fill it with the following contents:
"source_ami": "ami-de0d9eb7",
"instance_type": "t1.micro",
"ssh_username": "ubuntu",
"ami_name": "packer-example {{.CreateTime}}"
"ami_name": "packer-example {{timestamp}}"
}]
}
</pre>
+2
View File
@@ -13,6 +13,7 @@
<li><a href="/docs/command-line/build.html">Build</a></li>
<li><a href="/docs/command-line/fix.html">Fix</a></li>
<li><a href="/docs/command-line/validate.html">Validate</a></li>
<li><a href="/docs/command-line/machine-readable.html">Machine-Readable Output</a></li>
</ul>
<ul>
@@ -22,6 +23,7 @@
<li><a href="/docs/templates/provisioners.html">Provisioners</a></li>
<li><a href="/docs/templates/post-processors.html">Post-Processors</a></li>
<li><a href="/docs/templates/configuration-templates.html">Configuration Templates</a></li>
<li><a href="/docs/templates/user-variables.html">User Variables</a></li>
<li><a href="/docs/templates/veewee-to-packer.html">Veewee-to-Packer</a></li>
</ul>

Some files were not shown because too many files have changed in this diff Show More