Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
packer
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kristopher Ruzic
packer
Commits
41a6fe9f
Commit
41a6fe9f
authored
May 27, 2015
by
Mitchell Hashimoto
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
template/interpolate: RenderMap to render a complex structure
parent
e59f09e9
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
365 additions
and
0 deletions
+365
-0
template/interpolate/render.go
template/interpolate/render.go
+273
-0
template/interpolate/render_test.go
template/interpolate/render_test.go
+92
-0
No files found.
template/interpolate/render.go
0 → 100644
View file @
41a6fe9f
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
once
sync
.
Once
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
}
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
}
f
.
once
.
Do
(
f
.
init
)
_
,
ok
:=
f
.
includeSet
[
k
]
return
ok
}
func
(
f
*
RenderFilter
)
init
()
{
f
.
includeSet
=
make
(
map
[
string
]
struct
{})
for
_
,
v
:=
range
f
.
Include
{
f
.
includeSet
[
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
)
}
}
template/interpolate/render_test.go
0 → 100644
View file @
41a6fe9f
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"
},
},
},
}
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
)
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment