cmd/hcldec: --var-refs option

This option skips the usual decoding step and instead prints out a JSON-
formatted list of the variables that are referenced by the configuration.

In simple cases this is not required, but for more complex use-cases it
can be useful to first analyze the input to see which variables need to
be in the scope, then construct a suitable set of variables before finally
decoding the input. For example, some of the variable values may be
expensive to produce.
This commit is contained in:
Martin Atkins 2018-08-09 17:40:14 -07:00
parent bb724af7fd
commit 609cc35d49
1 changed files with 108 additions and 0 deletions

View File

@ -26,6 +26,7 @@ var vars = &varSpecs{}
var (
specFile = flag.StringP("spec", "s", "", "path to spec file (required)")
outputFile = flag.StringP("out", "o", "", "write to the given file, instead of stdout")
showVarRefs = flag.BoolP("var-refs", "", false, "rather than decoding input, produce a JSON description of the variables referenced by it")
showVersion = flag.BoolP("version", "v", false, "show the version number and immediately exit")
)
@ -159,6 +160,11 @@ func realmain(args []string) error {
body = hcl.MergeBodies(bodies)
}
if *showVarRefs {
vars := hcldec.Variables(body, spec)
return showVarRefsJSON(vars, ctx)
}
val, decDiags := hcldec.Decode(body, spec, ctx)
diags = append(diags, decDiags...)
@ -197,6 +203,108 @@ func usage() {
os.Exit(2)
}
func showVarRefsJSON(vars []hcl.Traversal, ctx *hcl.EvalContext) error {
type PosJSON struct {
Line int `json:"line"`
Column int `json:"column"`
Byte int `json:"byte"`
}
type RangeJSON struct {
Filename string `json:"filename"`
Start PosJSON `json:"start"`
End PosJSON `json:"end"`
}
type StepJSON struct {
Kind string `json:"kind"`
Name string `json:"name,omitempty"`
Key json.RawMessage `json:"key,omitempty"`
Range RangeJSON `json:"range"`
}
type TraversalJSON struct {
RootName string `json:"root_name"`
Value json.RawMessage `json:"value,omitempty"`
Steps []StepJSON `json:"steps"`
Range RangeJSON `json:"range"`
}
ret := make([]TraversalJSON, 0, len(vars))
for _, traversal := range vars {
tJSON := TraversalJSON{
Steps: make([]StepJSON, 0, len(traversal)),
}
for _, step := range traversal {
var sJSON StepJSON
rng := step.SourceRange()
sJSON.Range.Filename = rng.Filename
sJSON.Range.Start.Line = rng.Start.Line
sJSON.Range.Start.Column = rng.Start.Column
sJSON.Range.Start.Byte = rng.Start.Byte
sJSON.Range.End.Line = rng.End.Line
sJSON.Range.End.Column = rng.End.Column
sJSON.Range.End.Byte = rng.End.Byte
switch ts := step.(type) {
case hcl.TraverseRoot:
sJSON.Kind = "root"
sJSON.Name = ts.Name
tJSON.RootName = ts.Name
case hcl.TraverseAttr:
sJSON.Kind = "attr"
sJSON.Name = ts.Name
case hcl.TraverseIndex:
sJSON.Kind = "index"
src, err := ctyjson.Marshal(ts.Key, ts.Key.Type())
if err == nil {
sJSON.Key = json.RawMessage(src)
}
default:
// Should never get here, since the above should be exhaustive
// for all possible traversal step types.
sJSON.Kind = "(unknown)"
}
tJSON.Steps = append(tJSON.Steps, sJSON)
}
// Best effort, we'll try to include the current known value of this
// traversal, if any.
val, diags := traversal.TraverseAbs(ctx)
if !diags.HasErrors() {
enc, err := ctyjson.Marshal(val, val.Type())
if err == nil {
tJSON.Value = json.RawMessage(enc)
}
}
rng := traversal.SourceRange()
tJSON.Range.Filename = rng.Filename
tJSON.Range.Start.Line = rng.Start.Line
tJSON.Range.Start.Column = rng.Start.Column
tJSON.Range.Start.Byte = rng.Start.Byte
tJSON.Range.End.Line = rng.End.Line
tJSON.Range.End.Column = rng.End.Column
tJSON.Range.End.Byte = rng.End.Byte
ret = append(ret, tJSON)
}
out, err := json.MarshalIndent(ret, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal variable references as JSON: %s", err)
}
target := os.Stdout
if *outputFile != "" {
target, err = os.OpenFile(*outputFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return fmt.Errorf("can't open %s for writing: %s", *outputFile, err)
}
}
fmt.Fprintf(target, "%s\n", out)
return nil
}
func stripJSONNullProperties(src []byte) []byte {
var v interface{}
err := json.Unmarshal(src, &v)