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:
parent
bb724af7fd
commit
609cc35d49
@ -26,6 +26,7 @@ var vars = &varSpecs{}
|
|||||||
var (
|
var (
|
||||||
specFile = flag.StringP("spec", "s", "", "path to spec file (required)")
|
specFile = flag.StringP("spec", "s", "", "path to spec file (required)")
|
||||||
outputFile = flag.StringP("out", "o", "", "write to the given file, instead of stdout")
|
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")
|
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)
|
body = hcl.MergeBodies(bodies)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *showVarRefs {
|
||||||
|
vars := hcldec.Variables(body, spec)
|
||||||
|
return showVarRefsJSON(vars, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
val, decDiags := hcldec.Decode(body, spec, ctx)
|
val, decDiags := hcldec.Decode(body, spec, ctx)
|
||||||
diags = append(diags, decDiags...)
|
diags = append(diags, decDiags...)
|
||||||
|
|
||||||
@ -197,6 +203,108 @@ func usage() {
|
|||||||
os.Exit(2)
|
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 {
|
func stripJSONNullProperties(src []byte) []byte {
|
||||||
var v interface{}
|
var v interface{}
|
||||||
err := json.Unmarshal(src, &v)
|
err := json.Unmarshal(src, &v)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user