diff --git a/common/shell-local/config.go b/common/shell-local/config.go index bdf3dfbd7..46f2f8088 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -10,7 +10,6 @@ import ( "runtime" "strings" - "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/shell" configHelper "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -52,7 +51,7 @@ type Config struct { func Decode(config *Config, raws ...interface{}) error { // Create passthrough for build-generated data so we can fill it in once we know // it - config.ctx.Data = common.PlaceholderData() + config.ctx.Data = packer.BasicPlaceholderData() err := configHelper.Decode(&config, &configHelper.DecodeOpts{ Interpolate: true, diff --git a/common/step_provision.go b/common/step_provision.go index 2745a18b2..7b2233c77 100644 --- a/common/step_provision.go +++ b/common/step_provision.go @@ -22,34 +22,6 @@ import ( // Produces: // -// Provisioners interpolate most of their fields in the prepare stage; this -// placeholder map helps keep fields that are only generated at build time from -// accidentally being interpolated into empty strings at prepare time. -func PlaceholderData() map[string]string { - placeholderData := map[string]string{} - placeholderData["ID"] = "{{.ID}}" - // The following correspond to communicator-agnostic functions that are - // part of the SSH and WinRM communicator implementations. These functions - // are not part of the communicator interface, but are stored on the - // Communicator Config and return the appropriate values rather than - // depending on the actual communicator config values. E.g "Password" - // reprosents either WinRMPassword or SSHPassword, which makes this more - // useful if a template contains multiple builds. - placeholderData["Host"] = "{{.Host}}" - placeholderData["Port"] = "{{.Port}}" - placeholderData["User"] = "{{.User}}" - placeholderData["Password"] = "{{.Password}}" - placeholderData["ConnType"] = "{{.Type}}" - placeholderData["PACKER_RUN_UUID"] = "{{.PACKER_RUN_UUID}}" - placeholderData["SSHPublicKey"] = "{{.SSHPublicKey}}" - placeholderData["SSHPrivateKey"] = "{{.SSHPrivateKey}}" - - // Backwards-compatability: - placeholderData["WinRMPassword"] = "{{.WinRMPassword}}" - - return placeholderData -} - func PopulateProvisionHookData(state multistep.StateBag) map[string]interface{} { hookData := map[string]interface{}{} // instance_id is placed in state by the builders. diff --git a/helper/common/shared_state.go b/helper/common/shared_state.go index 87e111cfb..dce05beec 100644 --- a/helper/common/shared_state.go +++ b/helper/common/shared_state.go @@ -7,6 +7,11 @@ import ( "path/filepath" ) +// This is used in the BasicPlaceholderData() func in the packer/provisioner.go +// To force users to access generated data via the "generated" func. +const PlaceholderMsg = "To set this dynamically in the Packer template, " + + "you must use the `generated` function" + // Used to set variables which we need to access later in the build, where // state bag and config information won't work func sharedStateFilename(suffix string, buildName string) string { diff --git a/packer/provisioner.go b/packer/provisioner.go index 6e29e2e61..d20e4d360 100644 --- a/packer/provisioner.go +++ b/packer/provisioner.go @@ -6,6 +6,8 @@ import ( "log" "sync" "time" + + "github.com/hashicorp/packer/helper/common" ) // A provisioner is responsible for installing and configuring software @@ -37,6 +39,42 @@ type ProvisionHook struct { Provisioners []*HookedProvisioner } +// Provisioners interpolate most of their fields in the prepare stage; this +// placeholder map helps keep fields that are only generated at build time from +// accidentally being interpolated into empty strings at prepare time. +// This helper function generates the most basic placeholder data which should +// be accessible to the provisioners. It is used to initialize provisioners, to +// force validation using the `generated` template function. In the future, +// custom generated data could be passed into provisioners from builders to +// enable specialized builder-specific (but still validated!!) access to builder +// data. +func BasicPlaceholderData() map[string]string { + placeholderData := map[string]string{} + msg := "Generated_%s. " + common.PlaceholderMsg + placeholderData["ID"] = fmt.Sprintf(msg, "ID") + // The following correspond to communicator-agnostic functions that are + // part of the SSH and WinRM communicator implementations. These functions + // are not part of the communicator interface, but are stored on the + // Communicator Config and return the appropriate values rather than + // depending on the actual communicator config values. E.g "Password" + // reprosents either WinRMPassword or SSHPassword, which makes this more + // useful if a template contains multiple builds. + placeholderData["Host"] = fmt.Sprintf(msg, "Host") + placeholderData["Port"] = fmt.Sprintf(msg, "Port") + placeholderData["User"] = fmt.Sprintf(msg, "User") + placeholderData["Password"] = fmt.Sprintf(msg, "Password") + placeholderData["ConnType"] = fmt.Sprintf(msg, "Type") + placeholderData["PACKER_RUN_UUID"] = fmt.Sprintf(msg, "PACKER_RUN_UUID") + placeholderData["SSHPublicKey"] = fmt.Sprintf(msg, "SSHPublicKey") + placeholderData["SSHPrivateKey"] = fmt.Sprintf(msg, "SSHPrivateKey") + + // Backwards-compatability: WinRM Password can get through without forcing + // the generated func validation. + placeholderData["WinRMPassword"] = "{{.WinRMPassword}}" + + return placeholderData +} + // Runs the provisioners in order. func (h *ProvisionHook) Run(ctx context.Context, name string, ui Ui, comm Communicator, data interface{}) error { // Shortcut diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index bdcf578c3..201bfd086 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -81,7 +81,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.done = make(chan struct{}) // Create passthrough for build-generated data - p.config.ctx.Data = common.PlaceholderData() + p.config.ctx.Data = packer.BasicPlaceholderData() err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/provisioner/chef-client/provisioner.go b/provisioner/chef-client/provisioner.go index 399a3c847..a0d33e4f3 100644 --- a/provisioner/chef-client/provisioner.go +++ b/provisioner/chef-client/provisioner.go @@ -121,7 +121,7 @@ type KnifeTemplate struct { func (p *Provisioner) Prepare(raws ...interface{}) error { // Create passthrough for build-generated data - p.config.ctx.Data = common.PlaceholderData() + p.config.ctx.Data = packer.BasicPlaceholderData() err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &p.config.ctx, diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index c0d567627..71ef42765 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -90,7 +90,7 @@ func (p *Provisioner) defaultExecuteCommand() string { func (p *Provisioner) Prepare(raws ...interface{}) error { // Create passthrough for build-generated data - p.config.ctx.Data = common.PlaceholderData() + p.config.ctx.Data = packer.BasicPlaceholderData() err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index 7a6a968ed..289b0e6c9 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -148,7 +148,7 @@ type ExecuteTemplate struct { func (p *Provisioner) Prepare(raws ...interface{}) error { // Create passthrough for build-generated data - p.config.ctx.Data = common.PlaceholderData() + p.config.ctx.Data = packer.BasicPlaceholderData() err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &p.config.ctx, diff --git a/provisioner/puppet-server/provisioner.go b/provisioner/puppet-server/provisioner.go index fd30d46d4..ba7cf4584 100644 --- a/provisioner/puppet-server/provisioner.go +++ b/provisioner/puppet-server/provisioner.go @@ -142,7 +142,7 @@ type ExecuteTemplate struct { func (p *Provisioner) Prepare(raws ...interface{}) error { // Create passthrough for build-generated data - p.config.ctx.Data = common.PlaceholderData() + p.config.ctx.Data = packer.BasicPlaceholderData() err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/template/interpolate/funcs.go b/template/interpolate/funcs.go index 2373ded1b..96b04ff67 100644 --- a/template/interpolate/funcs.go +++ b/template/interpolate/funcs.go @@ -12,6 +12,7 @@ import ( consulapi "github.com/hashicorp/consul/api" "github.com/hashicorp/packer/common/uuid" + "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/version" vaultapi "github.com/hashicorp/vault/api" strftime "github.com/jehiah/go-strftime" @@ -166,11 +167,17 @@ func funcGenTemplateDir(ctx *Context) interface{} { func funcGenGenerated(ctx *Context) interface{} { return func(s string) (string, error) { if data, ok := ctx.Data.(map[string]string); ok { - // PlaceholderData has been passed into generator, and we can check - // the value against the placeholder data to make sure that it is - // valid + // PlaceholderData has been passed into generator, so if the given + // key already exists in data, then we know it's an "allowed" key if heldPlace, ok := data[s]; ok { - return heldPlace, nil + // If we're in the first interpolation pass, the goal is to + // make sure that we pass the value through. + // TODO match against an actual string constant + if strings.Contains(heldPlace, common.PlaceholderMsg) { + return fmt.Sprintf("{{.%s}}", s), nil + } else { + return heldPlace, nil + } } else { return "", fmt.Errorf("loaded data, but couldnt find %s in it", s) }