Skip to content

Commit

Permalink
[hcledit/read] fallback to raw values if unparsable (#100)
Browse files Browse the repository at this point in the history
* [hcledit/read] fallback to raw values if unparsable

* [hcledit/read] add option to give clients ability to enable fallback behavior

* [hcledit/read] add flag to CLI command to control this behavior
  • Loading branch information
ryan-ph authored Apr 24, 2024
1 parent 07a8450 commit f186c5c
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 17 deletions.
3 changes: 3 additions & 0 deletions cmd/hcledit/internal/command/fixture/file.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ module "my-module" {
"f",
]

unevaluateable_reference = var.name
unevaluateable_interpolation = "this-${local.reference}"

map_variable = {
bool_variable = true
int_variable = 1
Expand Down
8 changes: 7 additions & 1 deletion cmd/hcledit/internal/command/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

type ReadOptions struct {
OutputFormat string
Fallback bool
}

func NewCmdRead() *cobra.Command {
Expand All @@ -36,6 +37,7 @@ func NewCmdRead() *cobra.Command {
}

cmd.Flags().StringVarP(&opts.OutputFormat, "output-format", "o", "go-template='{{.Value}}'", "format to print the value as")
cmd.Flags().BoolVar(&opts.Fallback, "fallback", false, "falls back to reading the raw value if it cannot be evaluated")

return cmd
}
Expand All @@ -48,7 +50,11 @@ func runRead(opts *ReadOptions, args []string) (string, error) {
return "", fmt.Errorf("failed to read file: %s", err)
}

results, err := editor.Read(query)
readOpts:= []hcledit.Option{}
if opts.Fallback {
readOpts = append(readOpts, hcledit.WithReadFallbackToRawString())
}
results, err := editor.Read(query, readOpts...)
if err != nil {
return "", fmt.Errorf("failed to read file: %s", err)
}
Expand Down
16 changes: 16 additions & 0 deletions cmd/hcledit/internal/command/read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,22 @@ func TestRunRead(t *testing.T) {
want: "",
opts: defaultOpts,
},
"unevaluateable reference fallback": {
query: "module.my-module.unevaluateable_reference",
want: "var.name",
opts: &ReadOptions{
OutputFormat: "go-template='{{.Value}}'",
Fallback: true,
},
},
"unevaluateable interpolation fallback": {
query: "module.my-module.unevaluateable_interpolation",
want: `"this-${local.reference}"`,
opts: &ReadOptions{
OutputFormat: "go-template='{{.Value}}'",
Fallback: true,
},
},
}

for name, tc := range cases {
Expand Down
2 changes: 1 addition & 1 deletion hcledit.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (h *HCLEditor) Read(queryStr string, opts ...Option) (map[string]interface{
}

results := make(map[string]cty.Value)
hdlr, err := handler.NewReadHandler(results)
hdlr, err := handler.NewReadHandler(results, opt.readFallbackToRawString)
if err != nil {
return nil, err
}
Expand Down
45 changes: 41 additions & 4 deletions hcledit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,15 +190,17 @@ object1 = {

func TestRead(t *testing.T) {
cases := map[string]struct {
input string
query string
want map[string]interface{}
input string
query string
options []hcledit.Option
want map[string]interface{}
}{
"Attribute": {
input: `
attribute = "R"
`,
query: "attribute",
options: make([]hcledit.Option, 0),
want: map[string]interface{}{
"attribute": "R",
},
Expand All @@ -211,6 +213,7 @@ block "label1" "label2" {
attribute = "str"
}
`,
options: make([]hcledit.Option, 0),
query: "block",
want: map[string]interface{}{},
},
Expand All @@ -221,6 +224,7 @@ block "label1" "label2" {
attribute = "R"
}
`,
options: make([]hcledit.Option, 0),
query: "block.label1.label2.attribute",
want: map[string]interface{}{
"block.label1.label2.attribute": "R",
Expand All @@ -235,6 +239,7 @@ block1 "label1" "label2" {
}
}
`,
options: make([]hcledit.Option, 0),
query: "block1.label1.label2.block2.label3.label4.attribute",
want: map[string]interface{}{
"block1.label1.label2.block2.label3.label4.attribute": "R",
Expand All @@ -252,6 +257,7 @@ block "label" "label2" {
}
`,
options: make([]hcledit.Option, 0),
query: "block.label.*.attribute",
want: map[string]interface{}{
"block.label.label1.attribute": "R",
Expand All @@ -265,6 +271,7 @@ object = {
attribute = "R"
}
`,
options: make([]hcledit.Option, 0),
query: "object.attribute",
want: map[string]interface{}{
"object.attribute": "R",
Expand All @@ -278,6 +285,7 @@ object1 = {
}
}
`,
options: make([]hcledit.Option, 0),
query: "object1.object2.attribute",
want: map[string]interface{}{
"object1.object2.attribute": "R",
Expand All @@ -288,6 +296,7 @@ object1 = {
input: `
attribute = 1
`,
options: make([]hcledit.Option, 0),
query: "attribute",
want: map[string]interface{}{
"attribute": 1,
Expand All @@ -298,6 +307,7 @@ attribute = 1
input: `
attribute = "str"
`,
options: make([]hcledit.Option, 0),
query: "attribute",
want: map[string]interface{}{
"attribute": "str",
Expand All @@ -308,6 +318,7 @@ attribute = "str"
input: `
attribute = true
`,
options: make([]hcledit.Option, 0),
query: "attribute",
want: map[string]interface{}{
"attribute": true,
Expand All @@ -318,6 +329,7 @@ attribute = true
input: `
attribute = false
`,
options: make([]hcledit.Option, 0),
query: "attribute",
want: map[string]interface{}{
"attribute": false,
Expand All @@ -328,6 +340,7 @@ attribute = false
input: `
attribute = ["str1", "str2", "str3"]
`,
options: make([]hcledit.Option, 0),
query: "attribute",
want: map[string]interface{}{
"attribute": []string{"str1", "str2", "str3"},
Expand All @@ -338,6 +351,7 @@ attribute = ["str1", "str2", "str3"]
input: `
attribute = [1, 2, 3]
`,
options: make([]hcledit.Option, 0),
query: "attribute",
want: map[string]interface{}{
"attribute": []int{1, 2, 3},
Expand All @@ -348,11 +362,34 @@ attribute = [1, 2, 3]
input: `
attribute = [true, false, true]
`,
options: make([]hcledit.Option, 0),
query: "attribute",
want: map[string]interface{}{
"attribute": []bool{true, false, true},
},
},

"fallback to absolute variable name": {
input: `
attribute = local.var
`,
options: []hcledit.Option{hcledit.WithReadFallbackToRawString()},
query: "attribute",
want: map[string]interface{}{
"attribute": "local.var",
},
},

"fallback to uninterpolated string": {
input: `
attribute = "some-${local.var}"
`,
options: []hcledit.Option{hcledit.WithReadFallbackToRawString()},
query: "attribute",
want: map[string]interface{}{
"attribute": `"some-${local.var}"`,
},
},
}

for name, tc := range cases {
Expand All @@ -363,7 +400,7 @@ attribute = [true, false, true]
t.Fatal(err)
}

got, err := editor.Read(tc.query)
got, err := editor.Read(tc.query, tc.options...)
if err != nil {
t.Fatal(err)
}
Expand Down
25 changes: 17 additions & 8 deletions internal/handler/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ import (
)

type readHandler struct {
results map[string]cty.Value
results map[string]cty.Value
fallbackToRawString bool
}

func NewReadHandler(results map[string]cty.Value) (Handler, error) {
func NewReadHandler(results map[string]cty.Value, fallbackToRawString bool) (Handler, error) {
return &readHandler{
results: results,
results: results,
fallbackToRawString: fallbackToRawString,
}, nil
}

func (h *readHandler) HandleBody(body *hclwrite.Body, name string, keyTrail []string) error {
buf := body.GetAttribute(name).BuildTokens(nil).Bytes()
value, err := parse(buf, name)
value, err := parse(buf, name, h.fallbackToRawString)
if err != nil {
return err
}
Expand All @@ -33,24 +35,31 @@ func (h *readHandler) HandleBody(body *hclwrite.Body, name string, keyTrail []st

func (h *readHandler) HandleObject(object *ast.Object, name string, keyTrail []string) error {
buf := object.GetObjectAttribute(name).BuildTokens().Bytes()
value, err := parse(buf, name)
value, err := parse(buf, name, h.fallbackToRawString)
if err != nil {
return err
}
h.results[strings.Join(keyTrail, ".")] = value
return nil
}

func parse(buf []byte, name string) (cty.Value, error) {
func parse(buf []byte, name string, fallback bool) (cty.Value, error) {
file, diags := hclsyntax.ParseConfig(buf, "", hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
return cty.Value{}, diags
}

body := file.Body.(*hclsyntax.Body)
v, diags := body.Attributes[name].Expr.Value(nil)
expr := body.Attributes[name].Expr
v, diags := expr.Value(nil)
if diags.HasErrors() {
return cty.Value{}, diags
if !fallback {
return cty.Value{}, diags
}

// Could not parse the value with a nil EvalContext, so this is likely an
// interpolated string. Instead, attempt to parse the raw string value.
return cty.StringVal(string(expr.Range().SliceBytes(buf))), nil
}
return v, nil
}
13 changes: 10 additions & 3 deletions option.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package hcledit

type option struct {
comment string
afterKey string
beforeNewline bool
comment string
afterKey string
beforeNewline bool
readFallbackToRawString bool
}

// Option configures specific behavior for specific HCLEditor operations.
Expand All @@ -30,3 +31,9 @@ func WithNewLine() Option {
opt.beforeNewline = true
}
}

func WithReadFallbackToRawString() Option {
return func(opt *option) {
opt.readFallbackToRawString = true
}
}

0 comments on commit f186c5c

Please sign in to comment.