Merge pull request #2145 from mitchellh/f-interpolate
Much more automated interpolation
This commit is contained in:
commit
8d480d6992
|
@ -6,7 +6,6 @@ package chroot
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
|
||||
|
@ -14,7 +13,9 @@ import (
|
|||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// The unique ID for this builder
|
||||
|
@ -34,7 +35,7 @@ type Config struct {
|
|||
MountPath string `mapstructure:"mount_path"`
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
ctx *interpolate.Context
|
||||
}
|
||||
|
||||
type wrappedCommandTemplate struct {
|
||||
|
@ -47,18 +48,21 @@ type Builder struct {
|
|||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
md, err := common.DecodeConfig(&b.config, raws...)
|
||||
b.config.ctx = &interpolate.Context{Funcs: awscommon.TemplateFuncs}
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"command_wrapper",
|
||||
"mount_path",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.config.tpl.UserVars = b.config.PackerUserVars
|
||||
b.config.tpl.Funcs(awscommon.TemplateFuncs)
|
||||
|
||||
// Defaults
|
||||
if b.config.ChrootMounts == nil {
|
||||
b.config.ChrootMounts = make([][]string, 0)
|
||||
|
@ -91,55 +95,22 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
|
||||
// Accumulate any errors
|
||||
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)...)
|
||||
var errs *packer.MultiError
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.ctx)...)
|
||||
|
||||
for i, mounts := range b.config.ChrootMounts {
|
||||
for _, 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,
|
||||
"source_ami": &b.config.SourceAmi,
|
||||
}
|
||||
|
||||
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 nil, errs
|
||||
}
|
||||
|
@ -166,10 +137,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
ec2conn := ec2.New(auth, region)
|
||||
|
||||
wrappedCommand := func(command string) (string, error) {
|
||||
return b.config.tpl.Process(
|
||||
b.config.CommandWrapper, &wrappedCommandTemplate{
|
||||
Command: command,
|
||||
})
|
||||
ctx := *b.config.ctx
|
||||
ctx.Data = &wrappedCommandTemplate{Command: command}
|
||||
return interpolate.Render(b.config.CommandWrapper, &ctx)
|
||||
}
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
|
|
|
@ -3,12 +3,14 @@ package chroot
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type mountPathData struct {
|
||||
|
@ -31,9 +33,9 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
|
|||
device := state.Get("device").(string)
|
||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||
|
||||
mountPath, err := config.tpl.Process(config.MountPath, &mountPathData{
|
||||
Device: filepath.Base(device),
|
||||
})
|
||||
ctx := *config.ctx
|
||||
ctx.Data = &mountPathData{Device: filepath.Base(device)}
|
||||
mountPath, err := interpolate.Render(config.MountPath, &ctx)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error preparing mount directory: %s", err)
|
||||
|
|
|
@ -2,10 +2,11 @@ package common
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// AccessConfig is for common configuration related to AWS access
|
||||
|
@ -49,31 +50,8 @@ func (c *AccessConfig) Region() (aws.Region, error) {
|
|||
return aws.Regions[region], nil
|
||||
}
|
||||
|
||||
func (c *AccessConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||
if t == nil {
|
||||
var err error
|
||||
t, err = packer.NewConfigTemplate()
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
if c.RawRegion != "" {
|
||||
if _, ok := aws.Regions[c.RawRegion]; !ok {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", c.RawRegion))
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// AMIConfig is for common configuration related to creating AMIs.
|
||||
|
@ -20,49 +20,8 @@ type AMIConfig struct {
|
|||
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
|
||||
}
|
||||
|
||||
func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||
if t == nil {
|
||||
var err error
|
||||
t, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
}
|
||||
|
||||
templates := map[string]*string{
|
||||
"ami_name": &c.AMIName,
|
||||
"ami_description": &c.AMIDescription,
|
||||
"ami_virtualization_type": &c.AMIVirtType,
|
||||
}
|
||||
|
||||
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,
|
||||
"ami_regions": c.AMIRegions,
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
if c.AMIName == "" {
|
||||
errs = append(errs, fmt.Errorf("ami_name must be specified"))
|
||||
}
|
||||
|
@ -92,27 +51,6 @@ func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
|
|||
c.AMIRegions = regions
|
||||
}
|
||||
|
||||
newTags := make(map[string]string)
|
||||
for k, v := range c.AMITags {
|
||||
k, err := t.Process(k, nil)
|
||||
if err != nil {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("Error processing tag key %s: %s", k, err))
|
||||
continue
|
||||
}
|
||||
|
||||
v, err := t.Process(v, nil)
|
||||
if err != nil {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("Error processing tag value '%s': %s", v, err))
|
||||
continue
|
||||
}
|
||||
|
||||
newTags[k] = v
|
||||
}
|
||||
|
||||
c.AMITags = newTags
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// BlockDevice
|
||||
|
@ -44,48 +42,7 @@ func buildBlockDevices(b []BlockDevice) []ec2.BlockDeviceMapping {
|
|||
return blockDevices
|
||||
}
|
||||
|
||||
func (b *BlockDevices) Prepare(t *packer.ConfigTemplate) []error {
|
||||
if t == nil {
|
||||
var err error
|
||||
t, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
}
|
||||
|
||||
lists := map[string][]BlockDevice{
|
||||
"ami_block_device_mappings": b.AMIMappings,
|
||||
"launch_block_device_mappings": b.LaunchMappings,
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for outer, bds := range lists {
|
||||
for i := 0; i < len(bds); i++ {
|
||||
templates := map[string]*string{
|
||||
"device_name": &bds[i].DeviceName,
|
||||
"snapshot_id": &bds[i].SnapshotId,
|
||||
"virtual_name": &bds[i].VirtualName,
|
||||
"volume_type": &bds[i].VolumeType,
|
||||
}
|
||||
|
||||
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[%d].%s: %s",
|
||||
outer, i, n, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
func (b *BlockDevices) Prepare(ctx *interpolate.Context) []error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/common/uuid"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// RunConfig contains configuration for running an instance from a source
|
||||
|
@ -38,43 +38,7 @@ type RunConfig struct {
|
|||
sshTimeout time.Duration
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||
if t == nil {
|
||||
var err error
|
||||
t, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
}
|
||||
|
||||
templates := map[string]*string{
|
||||
"iam_instance_profile": &c.IamInstanceProfile,
|
||||
"instance_type": &c.InstanceType,
|
||||
"spot_price": &c.SpotPrice,
|
||||
"spot_price_auto_product": &c.SpotPriceAutoProduct,
|
||||
"ssh_timeout": &c.RawSSHTimeout,
|
||||
"ssh_username": &c.SSHUsername,
|
||||
"ssh_private_key_file": &c.SSHPrivateKeyFile,
|
||||
"source_ami": &c.SourceAmi,
|
||||
"subnet_id": &c.SubnetId,
|
||||
"temporary_key_pair_name": &c.TemporaryKeyPairName,
|
||||
"vpc_id": &c.VpcId,
|
||||
"availability_zone": &c.AvailabilityZone,
|
||||
"user_data": &c.UserData,
|
||||
"user_data_file": &c.UserDataFile,
|
||||
"security_group_id": &c.SecurityGroupId,
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
// Defaults
|
||||
if c.SSHPort == 0 {
|
||||
c.SSHPort = 22
|
||||
|
@ -90,7 +54,7 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
|
|||
}
|
||||
|
||||
// Validation
|
||||
var err error
|
||||
var errs []error
|
||||
if c.SourceAmi == "" {
|
||||
errs = append(errs, errors.New("A source_ami must be specified"))
|
||||
}
|
||||
|
@ -127,42 +91,7 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
|
|||
}
|
||||
}
|
||||
|
||||
sliceTemplates := map[string][]string{
|
||||
"security_group_ids": c.SecurityGroupIds,
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newTags := make(map[string]string)
|
||||
for k, v := range c.RunTags {
|
||||
k, err := t.Process(k, nil)
|
||||
if err != nil {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("Error processing tag key %s: %s", k, err))
|
||||
continue
|
||||
}
|
||||
|
||||
v, err := t.Process(v, nil)
|
||||
if err != nil {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("Error processing tag value '%s': %s", v, err))
|
||||
continue
|
||||
}
|
||||
|
||||
newTags[k] = v
|
||||
}
|
||||
|
||||
c.RunTags = newTags
|
||||
|
||||
var err error
|
||||
c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
|
||||
|
|
|
@ -13,46 +13,45 @@ import (
|
|||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// The unique ID for this builder
|
||||
const BuilderId = "mitchellh.amazonebs"
|
||||
|
||||
type config struct {
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
awscommon.AMIConfig `mapstructure:",squash"`
|
||||
awscommon.BlockDevices `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
ctx *interpolate.Context
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
config config
|
||||
config Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
md, err := common.DecodeConfig(&b.config, raws...)
|
||||
b.config.ctx = &interpolate.Context{Funcs: awscommon.TemplateFuncs}
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: b.config.ctx,
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.config.tpl.UserVars = b.config.PackerUserVars
|
||||
b.config.tpl.Funcs(awscommon.TemplateFuncs)
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.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)...)
|
||||
var errs *packer.MultiError
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.ctx)...)
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, errs
|
||||
|
|
|
@ -13,7 +13,7 @@ type stepCreateAMI struct {
|
|||
}
|
||||
|
||||
func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(config)
|
||||
config := state.Get("config").(Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
type stepModifyInstance struct{}
|
||||
|
||||
func (s *stepModifyInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(config)
|
||||
config := state.Get("config").(Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
|
|
@ -13,7 +13,9 @@ import (
|
|||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// The unique ID for this builder
|
||||
|
@ -38,7 +40,7 @@ type Config struct {
|
|||
X509KeyPath string `mapstructure:"x509_key_path"`
|
||||
X509UploadPath string `mapstructure:"x509_upload_path"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
ctx *interpolate.Context
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
|
@ -47,18 +49,21 @@ type Builder struct {
|
|||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
md, err := common.DecodeConfig(&b.config, raws...)
|
||||
b.config.ctx = &interpolate.Context{Funcs: awscommon.TemplateFuncs}
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"bundle_upload_command",
|
||||
"bundle_vol_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.config.tpl.UserVars = b.config.PackerUserVars
|
||||
b.config.tpl.Funcs(awscommon.TemplateFuncs)
|
||||
|
||||
if b.config.BundleDestination == "" {
|
||||
b.config.BundleDestination = "/tmp"
|
||||
}
|
||||
|
@ -97,43 +102,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.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))
|
||||
}
|
||||
}
|
||||
var errs *packer.MultiError
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.ctx)...)
|
||||
|
||||
if b.config.AccountId == "" {
|
||||
errs = packer.MultiErrorAppend(errs, errors.New("account_id is required"))
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type bundleCmdData struct {
|
||||
|
@ -32,7 +33,7 @@ func (s *StepBundleVolume) Run(state multistep.StateBag) multistep.StepAction {
|
|||
|
||||
// Bundle the volume
|
||||
var err error
|
||||
config.BundleVolCommand, err = config.tpl.Process(config.BundleVolCommand, bundleCmdData{
|
||||
config.ctx.Data = bundleCmdData{
|
||||
AccountId: config.AccountId,
|
||||
Architecture: instance.Architecture,
|
||||
CertPath: x509RemoteCertPath,
|
||||
|
@ -40,7 +41,8 @@ func (s *StepBundleVolume) Run(state multistep.StateBag) multistep.StepAction {
|
|||
KeyPath: x509RemoteKeyPath,
|
||||
Prefix: config.BundlePrefix,
|
||||
PrivatePath: config.X509UploadPath,
|
||||
})
|
||||
}
|
||||
config.BundleVolCommand, err = interpolate.Render(config.BundleVolCommand, config.ctx)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error processing bundle volume command: %s", err)
|
||||
state.Put("error", err)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type uploadCmdData struct {
|
||||
|
@ -35,14 +36,15 @@ func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
config.BundleUploadCommand, err = config.tpl.Process(config.BundleUploadCommand, uploadCmdData{
|
||||
config.ctx.Data = uploadCmdData{
|
||||
AccessKey: config.AccessKey,
|
||||
BucketName: config.S3Bucket,
|
||||
BundleDirectory: config.BundleDestination,
|
||||
ManifestPath: manifestPath,
|
||||
Region: region.Name,
|
||||
SecretKey: config.SecretKey,
|
||||
})
|
||||
}
|
||||
config.BundleUploadCommand, err = interpolate.Render(config.BundleUploadCommand, config.ctx)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error processing bundle upload command: %s", err)
|
||||
state.Put("error", err)
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// DecodeOpts are the options for decoding configuration.
|
||||
type DecodeOpts struct {
|
||||
// Interpolate, if true, will automatically interpolate the
|
||||
// configuration with the given InterpolateContext. User variables
|
||||
// will be automatically detected and added in-place to the given
|
||||
// context.
|
||||
Interpolate bool
|
||||
InterpolateContext *interpolate.Context
|
||||
InterpolateFilter *interpolate.RenderFilter
|
||||
}
|
||||
|
||||
// Decode decodes the configuration into the target and optionally
|
||||
// automatically interpolates all the configuration as it goes.
|
||||
func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error {
|
||||
if config == nil {
|
||||
config = &DecodeOpts{Interpolate: true}
|
||||
}
|
||||
|
||||
// Interpolate first
|
||||
if config.Interpolate {
|
||||
// Detect user variables from the raws and merge them into our context
|
||||
ctx, err := DetectContext(raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if config.InterpolateContext == nil {
|
||||
config.InterpolateContext = ctx
|
||||
} else {
|
||||
config.InterpolateContext.UserVariables = ctx.UserVariables
|
||||
}
|
||||
ctx = config.InterpolateContext
|
||||
|
||||
// Render everything
|
||||
for i, raw := range raws {
|
||||
m, err := interpolate.RenderMap(raw, ctx, config.InterpolateFilter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raws[i] = m
|
||||
}
|
||||
}
|
||||
|
||||
// Build our decoder
|
||||
var md mapstructure.Metadata
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
Result: target,
|
||||
Metadata: &md,
|
||||
WeaklyTypedInput: true,
|
||||
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
||||
uint8ToStringHook,
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, raw := range raws {
|
||||
if err := decoder.Decode(raw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If we have unused keys, it is an error
|
||||
if len(md.Unused) > 0 {
|
||||
var err error
|
||||
sort.Strings(md.Unused)
|
||||
for _, unused := range md.Unused {
|
||||
if unused != "type" && !strings.HasPrefix(unused, "packer_") {
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"unknown configuration key: %q", unused))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetectContext builds a base interpolate.Context, automatically
|
||||
// detecting things like user variables from the raw configuration params.
|
||||
func DetectContext(raws ...interface{}) (*interpolate.Context, error) {
|
||||
var s struct {
|
||||
Vars map[string]string `mapstructure:"packer_user_variables"`
|
||||
}
|
||||
|
||||
for _, r := range raws {
|
||||
if err := mapstructure.Decode(r, &s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &interpolate.Context{
|
||||
UserVariables: s.Vars,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func uint8ToStringHook(f reflect.Kind, t reflect.Kind, v interface{}) (interface{}, error) {
|
||||
// We need to convert []uint8 to string. We have to do this
|
||||
// because internally Packer uses MsgPack for RPC and the MsgPack
|
||||
// codec turns strings into []uint8
|
||||
if f == reflect.Slice && t == reflect.String {
|
||||
dataVal := reflect.ValueOf(v)
|
||||
dataType := dataVal.Type()
|
||||
elemKind := dataType.Elem().Kind()
|
||||
if elemKind == reflect.Uint8 {
|
||||
v = string(dataVal.Interface().([]uint8))
|
||||
}
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
type Target struct {
|
||||
Name string
|
||||
Address string
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
Input []interface{}
|
||||
Output *Target
|
||||
Opts *DecodeOpts
|
||||
}{
|
||||
"basic": {
|
||||
[]interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "bar",
|
||||
},
|
||||
},
|
||||
&Target{
|
||||
Name: "bar",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
|
||||
"variables": {
|
||||
[]interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "{{user `name`}}",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"packer_user_variables": map[string]string{
|
||||
"name": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
&Target{
|
||||
Name: "bar",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
|
||||
"filter": {
|
||||
[]interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "{{user `name`}}",
|
||||
"address": "{{user `name`}}",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"packer_user_variables": map[string]string{
|
||||
"name": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
&Target{
|
||||
Name: "bar",
|
||||
Address: "{{user `name`}}",
|
||||
},
|
||||
&DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Include: []string{"name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range cases {
|
||||
var result Target
|
||||
err := Decode(&result, tc.Opts, tc.Input...)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s\n\n%s", k, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(&result, tc.Output) {
|
||||
t.Fatalf("bad:\n\n%#v\n\n%#v", &result, tc.Output)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -45,6 +45,11 @@ func Funcs(ctx *Context) template.FuncMap {
|
|||
for k, v := range FuncGens {
|
||||
result[k] = v(ctx)
|
||||
}
|
||||
if ctx != nil {
|
||||
for k, v := range ctx.Funcs {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return template.FuncMap(result)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,9 @@ type Context struct {
|
|||
// Data is the data for the template that is available
|
||||
Data interface{}
|
||||
|
||||
// Funcs are extra functions available in the template
|
||||
Funcs map[string]interface{}
|
||||
|
||||
// UserVariables is the mapping of user variables that the
|
||||
// "user" function reads from.
|
||||
UserVariables map[string]string
|
||||
|
|
|
@ -0,0 +1,291 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
)
|
||||
|
||||
// RenderFilter is an option for filtering what gets rendered and
|
||||
// doesn't within an interface.
|
||||
type RenderFilter struct {
|
||||
Include []string
|
||||
Exclude []string
|
||||
|
||||
once sync.Once
|
||||
excludeSet map[string]struct{}
|
||||
includeSet map[string]struct{}
|
||||
}
|
||||
|
||||
// RenderMap renders all the strings in the given interface. The
|
||||
// interface must decode into a map[string]interface{}, but is left
|
||||
// as an interface{} type to ease backwards compatibility with the way
|
||||
// arguments are passed around in Packer.
|
||||
func RenderMap(v interface{}, ctx *Context, f *RenderFilter) (map[string]interface{}, error) {
|
||||
// First decode it into the map
|
||||
var m map[string]interface{}
|
||||
if err := mapstructure.Decode(v, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now go through each value and render it
|
||||
for k, raw := range m {
|
||||
if !f.include(k) {
|
||||
continue
|
||||
}
|
||||
|
||||
raw, err := RenderInterface(raw, ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("render '%s': %s", k, err)
|
||||
}
|
||||
|
||||
m[k] = raw
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// RenderInterface renders any value and returns the resulting value.
|
||||
func RenderInterface(v interface{}, ctx *Context) (interface{}, error) {
|
||||
f := func(v string) (string, error) {
|
||||
return Render(v, ctx)
|
||||
}
|
||||
|
||||
walker := &renderWalker{
|
||||
F: f,
|
||||
Replace: true,
|
||||
}
|
||||
err := reflectwalk.Walk(v, walker)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if walker.Top != nil {
|
||||
v = walker.Top
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Include checks whether a key should be included.
|
||||
func (f *RenderFilter) include(k string) bool {
|
||||
if f == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
k = strings.ToLower(k)
|
||||
|
||||
f.once.Do(f.init)
|
||||
if len(f.includeSet) > 0 {
|
||||
_, ok := f.includeSet[k]
|
||||
return ok
|
||||
}
|
||||
if len(f.excludeSet) > 0 {
|
||||
_, ok := f.excludeSet[k]
|
||||
return !ok
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *RenderFilter) init() {
|
||||
f.includeSet = make(map[string]struct{})
|
||||
for _, v := range f.Include {
|
||||
f.includeSet[strings.ToLower(v)] = struct{}{}
|
||||
}
|
||||
|
||||
f.excludeSet = make(map[string]struct{})
|
||||
for _, v := range f.Exclude {
|
||||
f.excludeSet[strings.ToLower(v)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// renderWalker implements interfaces for the reflectwalk package
|
||||
// (github.com/mitchellh/reflectwalk) that can be used to automatically
|
||||
// execute a callback for an interpolation.
|
||||
type renderWalker struct {
|
||||
// F is the function to call for every interpolation. It can be nil.
|
||||
//
|
||||
// If Replace is true, then the return value of F will be used to
|
||||
// replace the interpolation.
|
||||
F renderWalkerFunc
|
||||
Replace bool
|
||||
|
||||
// ContextF is an advanced version of F that also receives the
|
||||
// location of where it is in the structure. This lets you do
|
||||
// context-aware validation.
|
||||
ContextF renderWalkerContextFunc
|
||||
|
||||
// Top is the top value of the walk. This might get replaced if the
|
||||
// top value needs to be modified. It is valid to read after any walk.
|
||||
// If it is nil, it means the top wasn't replaced.
|
||||
Top interface{}
|
||||
|
||||
key []string
|
||||
lastValue reflect.Value
|
||||
loc reflectwalk.Location
|
||||
cs []reflect.Value
|
||||
csKey []reflect.Value
|
||||
csData interface{}
|
||||
sliceIndex int
|
||||
unknownKeys []string
|
||||
}
|
||||
|
||||
// renderWalkerFunc is the callback called by interpolationWalk.
|
||||
// It is called with any interpolation found. It should return a value
|
||||
// to replace the interpolation with, along with any errors.
|
||||
//
|
||||
// If Replace is set to false in renderWalker, then the replace
|
||||
// value can be anything as it will have no effect.
|
||||
type renderWalkerFunc func(string) (string, error)
|
||||
|
||||
// renderWalkerContextFunc is called by interpolationWalk if
|
||||
// ContextF is set. This receives both the interpolation and the location
|
||||
// where the interpolation is.
|
||||
//
|
||||
// This callback can be used to validate the location of the interpolation
|
||||
// within the configuration.
|
||||
type renderWalkerContextFunc func(reflectwalk.Location, string)
|
||||
|
||||
func (w *renderWalker) Enter(loc reflectwalk.Location) error {
|
||||
w.loc = loc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *renderWalker) Exit(loc reflectwalk.Location) error {
|
||||
w.loc = reflectwalk.None
|
||||
|
||||
switch loc {
|
||||
case reflectwalk.Map:
|
||||
w.cs = w.cs[:len(w.cs)-1]
|
||||
case reflectwalk.MapValue:
|
||||
w.key = w.key[:len(w.key)-1]
|
||||
w.csKey = w.csKey[:len(w.csKey)-1]
|
||||
case reflectwalk.Slice:
|
||||
// Split any values that need to be split
|
||||
w.cs = w.cs[:len(w.cs)-1]
|
||||
case reflectwalk.SliceElem:
|
||||
w.csKey = w.csKey[:len(w.csKey)-1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *renderWalker) Map(m reflect.Value) error {
|
||||
w.cs = append(w.cs, m)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *renderWalker) MapElem(m, k, v reflect.Value) error {
|
||||
w.csData = k
|
||||
w.csKey = append(w.csKey, k)
|
||||
w.key = append(w.key, k.String())
|
||||
w.lastValue = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *renderWalker) Slice(s reflect.Value) error {
|
||||
w.cs = append(w.cs, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *renderWalker) SliceElem(i int, elem reflect.Value) error {
|
||||
w.csKey = append(w.csKey, reflect.ValueOf(i))
|
||||
w.sliceIndex = i
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *renderWalker) Primitive(v reflect.Value) error {
|
||||
setV := v
|
||||
|
||||
// We only care about strings
|
||||
if v.Kind() == reflect.Interface {
|
||||
setV = v
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Kind() != reflect.String {
|
||||
return nil
|
||||
}
|
||||
|
||||
strV := v.String()
|
||||
if w.ContextF != nil {
|
||||
w.ContextF(w.loc, strV)
|
||||
}
|
||||
|
||||
if w.F == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
replaceVal, err := w.F(strV)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"%s in:\n\n%s",
|
||||
err, v.String())
|
||||
}
|
||||
|
||||
if w.Replace {
|
||||
resultVal := reflect.ValueOf(replaceVal)
|
||||
switch w.loc {
|
||||
case reflectwalk.MapKey:
|
||||
m := w.cs[len(w.cs)-1]
|
||||
|
||||
// Delete the old value
|
||||
var zero reflect.Value
|
||||
m.SetMapIndex(w.csData.(reflect.Value), zero)
|
||||
|
||||
// Set the new key with the existing value
|
||||
m.SetMapIndex(resultVal, w.lastValue)
|
||||
|
||||
// Set the key to be the new key
|
||||
w.csData = resultVal
|
||||
case reflectwalk.MapValue:
|
||||
// If we're in a map, then the only way to set a map value is
|
||||
// to set it directly.
|
||||
m := w.cs[len(w.cs)-1]
|
||||
mk := w.csData.(reflect.Value)
|
||||
m.SetMapIndex(mk, resultVal)
|
||||
case reflectwalk.WalkLoc:
|
||||
// At the root element, we can't write that, so we just save it
|
||||
w.Top = resultVal.Interface()
|
||||
default:
|
||||
// Otherwise, we should be addressable
|
||||
setV.Set(resultVal)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *renderWalker) removeCurrent() {
|
||||
// Append the key to the unknown keys
|
||||
w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
|
||||
|
||||
for i := 1; i <= len(w.cs); i++ {
|
||||
c := w.cs[len(w.cs)-i]
|
||||
switch c.Kind() {
|
||||
case reflect.Map:
|
||||
// Zero value so that we delete the map key
|
||||
var val reflect.Value
|
||||
|
||||
// Get the key and delete it
|
||||
k := w.csData.(reflect.Value)
|
||||
c.SetMapIndex(k, val)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
panic("No container found for removeCurrent")
|
||||
}
|
||||
|
||||
func (w *renderWalker) replaceCurrent(v reflect.Value) {
|
||||
c := w.cs[len(w.cs)-2]
|
||||
switch c.Kind() {
|
||||
case reflect.Map:
|
||||
// Get the key and delete it
|
||||
k := w.csKey[len(w.csKey)-1]
|
||||
c.SetMapIndex(k, v)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRenderMap(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Input interface{}
|
||||
Output interface{}
|
||||
Filter *RenderFilter
|
||||
}{
|
||||
"basic": {
|
||||
map[string]interface{}{
|
||||
"foo": "{{upper `bar`}}",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": "BAR",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
|
||||
"map keys shouldn't be interpolated": {
|
||||
map[string]interface{}{
|
||||
"{{foo}}": "{{upper `bar`}}",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"{{foo}}": "BAR",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
|
||||
"nested values": {
|
||||
map[string]interface{}{
|
||||
"foo": map[string]string{
|
||||
"bar": "{{upper `baz`}}",
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": map[string]string{
|
||||
"bar": "BAZ",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
|
||||
"nested value keys": {
|
||||
map[string]interface{}{
|
||||
"foo": map[string]string{
|
||||
"{{upper `bar`}}": "{{upper `baz`}}",
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": map[string]string{
|
||||
"BAR": "BAZ",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
|
||||
"filter": {
|
||||
map[string]interface{}{
|
||||
"bar": "{{upper `baz`}}",
|
||||
"foo": map[string]string{
|
||||
"{{upper `bar`}}": "{{upper `baz`}}",
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bar": "BAZ",
|
||||
"foo": map[string]string{
|
||||
"{{upper `bar`}}": "{{upper `baz`}}",
|
||||
},
|
||||
},
|
||||
&RenderFilter{
|
||||
Include: []string{"bar"},
|
||||
},
|
||||
},
|
||||
|
||||
"filter case-insensitive": {
|
||||
map[string]interface{}{
|
||||
"bar": "{{upper `baz`}}",
|
||||
"foo": map[string]string{
|
||||
"{{upper `bar`}}": "{{upper `baz`}}",
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bar": "BAZ",
|
||||
"foo": map[string]string{
|
||||
"{{upper `bar`}}": "{{upper `baz`}}",
|
||||
},
|
||||
},
|
||||
&RenderFilter{
|
||||
Include: []string{"baR"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := &Context{}
|
||||
for k, tc := range cases {
|
||||
actual, err := RenderMap(tc.Input, ctx, tc.Filter)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s\n\n%s", k, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, tc.Output) {
|
||||
t.Fatalf("err: %s\n\n%#v\n\n%#v", k, actual, tc.Output)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue