Repository: lyraproj/hiera (go)

Issue:
Difference in facts flag behaviour with puppet lookup
Comes from #78

Given a `facts.yaml` file:
```
---
inventory: 'a'
```

For people like me coming from puppet, it's a bit weird/consufing that using `--facts facts.yaml` results in a different interpolation when using `lookup` (`inventory=a`) and when using `puppet lookup` (`facts.inventory=a`).

I feel that it should be at least a little documented somewhere (maybe it is and I haven't found it) or, for an easiest transition between tools, may current go implementation copy all facts to make them also available as facts map subkeys.

To give some background in our use case, we come from using Puppet + some scripting with hiera as our main source of truth and now we're replacing our scripting with tools like terraform but want to keep our hiera hierarchy as much as posible "as is".

Thanks for alll your fantastic work

/cc @thallgren

Pull request:
Give special meaning to --facts flag for Puppet compatibility
In Puppet, there's the concept of "facts" stored in the scope used for
lookups, both at the top level and in a special hash under the "facts"
key. Puppet fills the "facts" hash (and the top scope) by executing the
facter program which collects information from the machine where the
lookup is performed.

Go hiera doesn't really have "facts", but it does have a special option
named `--facts` which used to be an alias for `--vars`. This commit
changes this slightly so that variables obtained using the `--facts`
option ends up both in the top scope and in a a "facts" hash accessible
from the top scope, thus mimicking the Puppet behavior.

Closes #79

Patch:
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 7680d4e..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<component name="InspectionProjectProfileManager">
-  <profile version="1.0">
-    <option name="myName" value="Project Default" />
-    <inspection_tool class="GoUnhandledErrorResult" enabled="true" level="WARNING" enabled_by_default="true">
-      <methods>
-        <method importPath="hash" receiver="Hash" name="Write" />
-        <method importPath="strings" receiver="*Builder" name="Write" />
-        <method importPath="strings" receiver="*Builder" name="WriteByte" />
-        <method importPath="bytes" receiver="*Buffer" name="WriteRune" />
-        <method importPath="bytes" receiver="*Buffer" name="Write" />
-        <method importPath="bytes" receiver="*Buffer" name="WriteString" />
-        <method importPath="strings" receiver="*Builder" name="WriteString" />
-        <method importPath="bytes" receiver="*Buffer" name="WriteByte" />
-        <method importPath="strings" receiver="*Builder" name="WriteRune" />
-        <method importPath="math/rand" receiver="*Rand" name="Read" />
-        <method importPath="io" receiver="ReadCloser" name="Close" />
-      </methods>
-    </inspection_tool>
-    <inspection_tool class="GoUnusedExportedFunction" enabled="false" level="WARNING" enabled_by_default="false" />
-    <inspection_tool class="GoUnusedGlobalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
-    <inspection_tool class="PuppetUnresolved" enabled="false" level="WARNING" enabled_by_default="false" />
-    <inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="false">
-      <scope name="SpellCheck" level="TYPO" enabled="true">
-        <option name="processCode" value="true" />
-        <option name="processLiterals" value="true" />
-        <option name="processComments" value="true" />
-      </scope>
-      <option name="processCode" value="true" />
-      <option name="processLiterals" value="true" />
-      <option name="processComments" value="true" />
-    </inspection_tool>
-  </profile>
-</component>
\ No newline at end of file
diff --git a/cli/executelookup.go b/cli/executelookup.go
index 9afed4a..3612119 100644
--- a/cli/executelookup.go
+++ b/cli/executelookup.go
@@ -12,11 +12,10 @@ func ExecuteLookup(args ...string) (output []byte, err error) {
 	dflt = OptString{}
 	logLevel = ``
 	configPath = ``
-	facts = nil
 
 	cmd := NewCommand()
 	buf := new(bytes.Buffer)
-	cmd.SetOutput(buf)
+	cmd.SetOut(buf)
 	cmd.SetArgs(args)
 
 	err = cmd.Execute()
diff --git a/cli/hiera.go b/cli/hiera.go
index 04125da..49e9d4a 100644
--- a/cli/hiera.go
+++ b/cli/hiera.go
@@ -73,7 +73,6 @@ var (
 	logLevel   string
 	configPath string
 	dialect    string
-	facts      []string
 )
 
 // NewCommand creates the hiera Command
@@ -110,8 +109,8 @@ func NewCommand() *cobra.Command {
 		`path to a JSON or YAML file that contains key-value mappings to become variables for this lookup`)
 	flags.StringArrayVar(&cmdOpts.Variables, `var`, nil,
 		`a key:value or key=value where value is literal expressed using Puppet DSL`)
-	flags.StringArrayVar(&facts, `facts`, nil,
-		`alias for --vars for compatibility with Puppet's ruby version of Hiera`)
+	flags.StringArrayVar(&cmdOpts.FactPaths, `facts`, nil,
+		`like --vars but will also make variables available under the "facts" (for compatibility with Puppet's ruby version of Hiera)`)
 
 	cmd.SetHelpTemplate(helpTemplate)
 	return cmd
@@ -128,9 +127,6 @@ func cmdLookup(cmd *cobra.Command, args []string) error {
 	if configPath != `` {
 		cfgOpts.Put(api.HieraConfig, configPath)
 	}
-	if len(facts) > 0 {
-		cmdOpts.VarPaths = append(cmdOpts.VarPaths, facts...)
-	}
 
 	return hiera.TryWithParent(context.Background(), provider.MuxLookupKey, cfgOpts, func(c api.Session) error {
 		hiera.LookupAndRender(c, &cmdOpts, args, cmd.OutOrStdout())
diff --git a/hiera/hiera.go b/hiera/hiera.go
index 785adfb..ef13222 100644
--- a/hiera/hiera.go
+++ b/hiera/hiera.go
@@ -33,6 +33,10 @@ type CommandOptions struct {
 	// Default is a pointer to the string representation of a default value or nil if no default value exists
 	Default *string
 
+	// FactPaths are an optional paths to a files containing extra variables to add to the lookup scope
+	// and as a copy under the lookup scope "facts" key.
+	FactPaths []string
+
 	// VarPaths are an optional paths to a files containing extra variables to add to the lookup scope
 	VarPaths []string
 
@@ -230,7 +234,18 @@ func createScope(c api.Session, opts *CommandOptions) dgo.Map {
 		}
 	}
 
-	for _, vars := range opts.VarPaths {
+	addVarPaths(opts.VarPaths, scope)
+	if len(opts.FactPaths) > 0 {
+		facts := vf.MutableMap()
+		addVarPaths(opts.FactPaths, facts)
+		scope.PutAll(facts)
+		scope.Put(`facts`, facts)
+	}
+	return scope
+}
+
+func addVarPaths(varPaths []string, m dgo.Map) {
+	for _, vars := range varPaths {
 		var bs []byte
 		var err error
 		if vars == `-` {
@@ -242,7 +257,7 @@ func createScope(c api.Session, opts *CommandOptions) dgo.Map {
 			var yv dgo.Value
 			if yv, err = yaml.Unmarshal(bs); err == nil {
 				if data, ok := yv.(dgo.Map); ok {
-					scope.PutAll(data)
+					m.PutAll(data)
 				} else {
 					err = fmt.Errorf(`file '%s' does not contain a YAML hash`, vars)
 				}
@@ -252,5 +267,4 @@ func createScope(c api.Session, opts *CommandOptions) dgo.Map {
 			panic(err)
 		}
 	}
-	return scope
 }


Test patch:
diff --git a/lookup/testdata/glob_hiera.yaml b/lookup/testdata/glob_hiera.yaml
index dfac7c8..eb7cf62 100644
--- a/lookup/testdata/glob_hiera.yaml
+++ b/lookup/testdata/glob_hiera.yaml
@@ -6,6 +6,6 @@ defaults:
 
 hierarchy:
   - name: Glob string
-    glob: "expansion/{%{glob_string}}/*.yaml"
+    glob: "expansion/{%{facts.glob_string}}/*.yaml"
   - name: Common
     path: common.yaml