reorganize placeholder data call to live with provisioner implementation; force users to use the generated function, therefore forcing validation, for all variables except winrmpassword, by doing a simple string check against the placeholder data.

This commit is contained in:
Megan Marsh 2019-12-14 03:32:38 -08:00
parent ac570e0cc0
commit 82367a88f8
10 changed files with 60 additions and 39 deletions

View File

@ -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,

View File

@ -22,34 +22,6 @@ import (
// Produces:
// <nothing>
// 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.

View File

@ -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 {

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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)
}