Add a function to quote HCL strings
The strings we have in the variables may contain escaped double-quotes, which have already been parsed and had the `\`s removed. We need to re-escape these, but only if we are in the outer string and not inside an interpolation.
This commit is contained in:
parent
648fff9ba1
commit
8038e60a20
|
@ -29,10 +29,14 @@ func encodeHCL(i interface{}) ([]byte, error) {
|
|||
|
||||
// use the real hcl parser to verify our output, and format it canonically
|
||||
hcl, err = printer.Format(fakeAssignment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// now strip that first assignment off
|
||||
eq := regexp.MustCompile(`=\s+`).FindIndex(hcl)
|
||||
return hcl[eq[1]:], err
|
||||
|
||||
return hcl[eq[1]:], nil
|
||||
}
|
||||
|
||||
type encodeState struct {
|
||||
|
@ -98,11 +102,6 @@ func (e *encodeState) encodeMap(m map[string]interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (e *encodeState) encodeString(s string) error {
|
||||
_, err := fmt.Fprintf(e, "%q", s)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *encodeState) encodeInt(i interface{}) error {
|
||||
_, err := fmt.Fprintf(e, "%d", i)
|
||||
return err
|
||||
|
@ -112,3 +111,81 @@ func (e *encodeState) encodeFloat(f interface{}) error {
|
|||
_, err := fmt.Fprintf(e, "%f", f)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *encodeState) encodeString(s string) error {
|
||||
e.Write(quoteHCLString(s))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Quote an HCL string, which may contain interpolations.
|
||||
// Since the string was already parsed from HCL, we have to assume the
|
||||
// required characters are sanely escaped. All we need to do is escape double
|
||||
// quotes in the string, unless they are in an interpolation block.
|
||||
func quoteHCLString(s string) []byte {
|
||||
out := make([]byte, 0, len(s))
|
||||
out = append(out, '"')
|
||||
|
||||
// our parse states
|
||||
var (
|
||||
outer = 1 // the starting state for the string
|
||||
dollar = 2 // look for '{' in the next character
|
||||
interp = 3 // inside an interpolation block
|
||||
escape = 4 // take the next character and pop back to prev state
|
||||
)
|
||||
|
||||
// we could have nested interpolations
|
||||
state := stack{}
|
||||
state.push(outer)
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch state.peek() {
|
||||
case outer:
|
||||
switch s[i] {
|
||||
case '"':
|
||||
out = append(out, '\\')
|
||||
case '$':
|
||||
state.push(dollar)
|
||||
case '\\':
|
||||
state.push(escape)
|
||||
}
|
||||
case dollar:
|
||||
state.pop()
|
||||
switch s[i] {
|
||||
case '{':
|
||||
state.push(interp)
|
||||
case '\\':
|
||||
state.push(escape)
|
||||
}
|
||||
case interp:
|
||||
switch s[i] {
|
||||
case '}':
|
||||
state.pop()
|
||||
}
|
||||
case escape:
|
||||
state.pop()
|
||||
}
|
||||
|
||||
out = append(out, s[i])
|
||||
}
|
||||
|
||||
out = append(out, '"')
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
type stack []int
|
||||
|
||||
func (s *stack) push(i int) {
|
||||
*s = append(*s, i)
|
||||
}
|
||||
|
||||
func (s *stack) pop() int {
|
||||
last := len(*s) - 1
|
||||
i := (*s)[last]
|
||||
*s = (*s)[:last]
|
||||
return i
|
||||
}
|
||||
|
||||
func (s *stack) peek() int {
|
||||
return (*s)[len(*s)-1]
|
||||
}
|
||||
|
|
|
@ -397,21 +397,29 @@ func TestPush_tfvars(t *testing.T) {
|
|||
// make sure these dind't go missing for some reason
|
||||
for k, v := range variables {
|
||||
if !reflect.DeepEqual(client.UpsertOptions.Variables[k], v) {
|
||||
t.Fatalf("bad: %#v", client.UpsertOptions.Variables)
|
||||
t.Fatalf("bad: %#v", client.UpsertOptions.Variables[k])
|
||||
}
|
||||
}
|
||||
|
||||
//now check TFVVars
|
||||
|
||||
//now check TFVars
|
||||
tfvars := []atlas.TFVar{
|
||||
{"bar", "foo", false},
|
||||
{"baz", "{\n A = \"a\"\n B = \"b\"\n}\n", true},
|
||||
{"fob", "[\"a\", \"b\", \"c\"]\n", true},
|
||||
{"baz", `{
|
||||
A = "a"
|
||||
B = "b"
|
||||
interp = "${file("t.txt")}"
|
||||
}
|
||||
`, true},
|
||||
{"fob", `["a", "b", "c", "quotes \"in\" quotes"]` + "\n", true},
|
||||
{"foo", "bar", false},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(client.UpsertOptions.TFVars, tfvars) {
|
||||
t.Fatalf("bad tf_vars: %#v", client.UpsertOptions.TFVars)
|
||||
for i, expected := range tfvars {
|
||||
got := client.UpsertOptions.TFVars[i]
|
||||
if got != expected {
|
||||
t.Logf("%2d expected: %#v", i, expected)
|
||||
t.Logf(" got: %#v", got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -582,9 +590,10 @@ func pushTFVars() map[string]interface{} {
|
|||
"foo": "bar",
|
||||
"bar": "foo",
|
||||
"baz": map[string]interface{}{
|
||||
"A": "a",
|
||||
"B": "b",
|
||||
"A": "a",
|
||||
"B": "b",
|
||||
"interp": `${file("t.txt")}`,
|
||||
},
|
||||
"fob": []interface{}{"a", "b", "c"},
|
||||
"fob": []interface{}{"a", "b", "c", `quotes "in" quotes`},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
variable "foo" {}
|
||||
|
||||
variable "bar" {}
|
||||
|
||||
variable "baz" {
|
||||
type = "map"
|
||||
default = {
|
||||
"A" = "a"
|
||||
"B" = "b"
|
||||
}
|
||||
type = "map"
|
||||
|
||||
default = {
|
||||
"A" = "a"
|
||||
"B" = "b"
|
||||
interp = "${file("t.txt")}"
|
||||
}
|
||||
}
|
||||
|
||||
variable "fob" {
|
||||
type = "list"
|
||||
default = ["a", "b", "c"]
|
||||
type = "list"
|
||||
default = ["a", "b", "c", "quotes \"in\" quotes"]
|
||||
}
|
||||
|
||||
resource "test_instance" "foo" {}
|
||||
|
||||
atlas {
|
||||
name = "foo"
|
||||
name = "foo"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue