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
|
// use the real hcl parser to verify our output, and format it canonically
|
||||||
hcl, err = printer.Format(fakeAssignment)
|
hcl, err = printer.Format(fakeAssignment)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// now strip that first assignment off
|
// now strip that first assignment off
|
||||||
eq := regexp.MustCompile(`=\s+`).FindIndex(hcl)
|
eq := regexp.MustCompile(`=\s+`).FindIndex(hcl)
|
||||||
return hcl[eq[1]:], err
|
|
||||||
|
return hcl[eq[1]:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type encodeState struct {
|
type encodeState struct {
|
||||||
|
@ -98,11 +102,6 @@ func (e *encodeState) encodeMap(m map[string]interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encodeState) encodeString(s string) error {
|
|
||||||
_, err := fmt.Fprintf(e, "%q", s)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *encodeState) encodeInt(i interface{}) error {
|
func (e *encodeState) encodeInt(i interface{}) error {
|
||||||
_, err := fmt.Fprintf(e, "%d", i)
|
_, err := fmt.Fprintf(e, "%d", i)
|
||||||
return err
|
return err
|
||||||
|
@ -112,3 +111,81 @@ func (e *encodeState) encodeFloat(f interface{}) error {
|
||||||
_, err := fmt.Fprintf(e, "%f", f)
|
_, err := fmt.Fprintf(e, "%f", f)
|
||||||
return err
|
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
|
// make sure these dind't go missing for some reason
|
||||||
for k, v := range variables {
|
for k, v := range variables {
|
||||||
if !reflect.DeepEqual(client.UpsertOptions.Variables[k], v) {
|
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{
|
tfvars := []atlas.TFVar{
|
||||||
{"bar", "foo", false},
|
{"bar", "foo", false},
|
||||||
{"baz", "{\n A = \"a\"\n B = \"b\"\n}\n", true},
|
{"baz", `{
|
||||||
{"fob", "[\"a\", \"b\", \"c\"]\n", true},
|
A = "a"
|
||||||
|
B = "b"
|
||||||
|
interp = "${file("t.txt")}"
|
||||||
|
}
|
||||||
|
`, true},
|
||||||
|
{"fob", `["a", "b", "c", "quotes \"in\" quotes"]` + "\n", true},
|
||||||
{"foo", "bar", false},
|
{"foo", "bar", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(client.UpsertOptions.TFVars, tfvars) {
|
for i, expected := range tfvars {
|
||||||
t.Fatalf("bad tf_vars: %#v", client.UpsertOptions.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",
|
"foo": "bar",
|
||||||
"bar": "foo",
|
"bar": "foo",
|
||||||
"baz": map[string]interface{}{
|
"baz": map[string]interface{}{
|
||||||
"A": "a",
|
"A": "a",
|
||||||
"B": "b",
|
"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 "foo" {}
|
||||||
|
|
||||||
variable "bar" {}
|
variable "bar" {}
|
||||||
|
|
||||||
variable "baz" {
|
variable "baz" {
|
||||||
type = "map"
|
type = "map"
|
||||||
default = {
|
|
||||||
"A" = "a"
|
default = {
|
||||||
"B" = "b"
|
"A" = "a"
|
||||||
}
|
"B" = "b"
|
||||||
|
interp = "${file("t.txt")}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "fob" {
|
variable "fob" {
|
||||||
type = "list"
|
type = "list"
|
||||||
default = ["a", "b", "c"]
|
default = ["a", "b", "c", "quotes \"in\" quotes"]
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "test_instance" "foo" {}
|
resource "test_instance" "foo" {}
|
||||||
|
|
||||||
atlas {
|
atlas {
|
||||||
name = "foo"
|
name = "foo"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue