From 609cc35d49710798824b43cce9622fb08d20ecd9 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 9 Aug 2018 17:40:14 -0700 Subject: 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. --- cmd/hcldec/main.go | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) (limited to 'cmd') diff --git a/cmd/hcldec/main.go b/cmd/hcldec/main.go index 6bb3bf7..dde87a5 100644 --- a/cmd/hcldec/main.go +++ b/cmd/hcldec/main.go @@ -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) -- cgit v1.2.3