summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2018-11-12 23:22:06 -0500
committerDave Henderson <dhenderson@gmail.com>2018-11-15 13:40:17 -0500
commit6056ca97d889fb268cc287334bc644e6b6d487d7 (patch)
tree70c877188224219bb94d6b1c98894e53daf02497
parent79b79c1db7652ba763f0186360e92da7f4b30fbd (diff)
New --context flag for adding datasources to context
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
-rw-r--r--cmd/gomplate/main.go2
-rw-r--r--context.go31
-rw-r--r--context_test.go49
-rw-r--r--data/datasource.go3
-rw-r--r--docs/content/datasources.md6
-rw-r--r--docs/content/syntax.md7
-rw-r--r--docs/content/usage.md6
-rw-r--r--gomplate.go20
-rw-r--r--tests/integration/datasources_file_test.go12
9 files changed, 124 insertions, 12 deletions
diff --git a/cmd/gomplate/main.go b/cmd/gomplate/main.go
index a40c6f0c..46c4dfa5 100644
--- a/cmd/gomplate/main.go
+++ b/cmd/gomplate/main.go
@@ -113,6 +113,8 @@ func initFlags(command *cobra.Command) {
command.Flags().StringArrayVarP(&opts.DataSources, "datasource", "d", nil, "`datasource` in alias=URL form. Specify multiple times to add multiple sources.")
command.Flags().StringArrayVarP(&opts.DataSourceHeaders, "datasource-header", "H", nil, "HTTP `header` field in 'alias=Name: value' form to be provided on HTTP-based data sources. Multiples can be set.")
+ command.Flags().StringArrayVarP(&opts.Contexts, "context", "c", nil, "pre-load a `datasource` into the context, in alias=URL form. Use the special alias `.` to set the root context.")
+
command.Flags().StringArrayVarP(&opts.InputFiles, "file", "f", []string{"-"}, "Template `file` to process. Omit to use standard input, or use --in or --input-dir")
command.Flags().StringVarP(&opts.Input, "in", "i", "", "Template `string` to process (alternative to --file and --input-dir)")
command.Flags().StringVar(&opts.InputDir, "input-dir", "", "`directory` which is examined recursively for templates (alternative to --file and --in)")
diff --git a/context.go b/context.go
index 6c100dfb..ab50a86a 100644
--- a/context.go
+++ b/context.go
@@ -3,11 +3,12 @@ package gomplate
import (
"os"
"strings"
+
+ "github.com/hairyhenderson/gomplate/data"
)
// context for templates
-type context struct {
-}
+type context map[string]interface{}
// Env - Map environment variables for use in a template
func (c *context) Env() map[string]string {
@@ -18,3 +19,29 @@ func (c *context) Env() map[string]string {
}
return env
}
+
+func createContext(contexts []string, d *data.Data) (interface{}, error) {
+ var err error
+ ctx := &context{}
+ for _, context := range contexts {
+ a := parseAlias(context)
+ if a == "." {
+ return d.Datasource(a)
+ }
+ (*ctx)[a], err = d.Datasource(a)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return ctx, nil
+}
+
+func parseAlias(arg string) string {
+ parts := strings.SplitN(arg, "=", 2)
+ switch len(parts) {
+ case 1:
+ return strings.SplitN(parts[0], ".", 2)[0]
+ default:
+ return parts[0]
+ }
+}
diff --git a/context_test.go b/context_test.go
index 16492e9a..f2780356 100644
--- a/context_test.go
+++ b/context_test.go
@@ -1,9 +1,12 @@
package gomplate
import (
+ "net/url"
"os"
"testing"
+ "github.com/hairyhenderson/gomplate/data"
+
"github.com/stretchr/testify/assert"
)
@@ -19,3 +22,49 @@ func TestEnvGetsUpdatedEnvironment(t *testing.T) {
assert.NoError(t, os.Setenv("FOO", "foo"))
assert.Equal(t, c.Env()["FOO"], "foo")
}
+
+func TestCreateContext(t *testing.T) {
+ c, err := createContext(nil, nil)
+ assert.NoError(t, err)
+ assert.Empty(t, c)
+
+ fooURL := "env:///foo?type=application/yaml"
+ barURL := "env:///bar?type=application/yaml"
+ uf, _ := url.Parse(fooURL)
+ ub, _ := url.Parse(barURL)
+ d := &data.Data{
+ Sources: map[string]*data.Source{
+ "foo": {URL: uf},
+ ".": {URL: ub},
+ },
+ }
+ os.Setenv("foo", "foo: bar")
+ defer os.Unsetenv("foo")
+ c, err = createContext([]string{"foo=" + fooURL}, d)
+ assert.NoError(t, err)
+ assert.IsType(t, &context{}, c)
+ ctx := c.(*context)
+ ds := ((*ctx)["foo"]).(map[string]interface{})
+ assert.Equal(t, "bar", ds["foo"])
+
+ os.Setenv("bar", "bar: baz")
+ defer os.Unsetenv("bar")
+ c, err = createContext([]string{".=" + barURL}, d)
+ assert.NoError(t, err)
+ assert.IsType(t, map[string]interface{}{}, c)
+ ds = c.(map[string]interface{})
+ assert.Equal(t, "baz", ds["bar"])
+}
+
+func TestParseAlias(t *testing.T) {
+ testdata := map[string]string{
+ "": "",
+ "foo": "foo",
+ "foo.bar": "foo",
+ "a=b": "a",
+ ".=foo": ".",
+ }
+ for k, v := range testdata {
+ assert.Equal(t, v, parseAlias(k))
+ }
+}
diff --git a/data/datasource.go b/data/datasource.go
index 0e676e5b..2e4d845d 100644
--- a/data/datasource.go
+++ b/data/datasource.go
@@ -267,6 +267,9 @@ func (d *Data) lookupSource(alias string) (*Source, error) {
}
d.Sources[alias] = source
}
+ if source.Alias == "" {
+ source.Alias = alias
+ }
return source, nil
}
diff --git a/docs/content/datasources.md b/docs/content/datasources.md
index 99e1dd58..e7dd5c9f 100644
--- a/docs/content/datasources.md
+++ b/docs/content/datasources.md
@@ -6,9 +6,9 @@ menu: main
Datasources are an optional, but central concept in gomplate. While the basic flow of template rendering is taking an input template and rendering it into an output, there often is need to include data from one or more sources external to the template itself.
-Some common use-cases include injecting sensitive material like passwords (which should not be stored in source-control with the templates), or providing simplified configuration formats that can be fed to a template to provide a much more complex output.
+Some common use-cases include injecting sensitive material like passwords (which should not be stored unencrypted in source-control with the templates), or providing simplified configuration formats that can be fed to a template to provide a much more complex output.
-Datasources can be defined with the [`--datasource`/`-d`][] command-line flag or the [`defineDatasource`][] function, and referenced via an _alias_ inside the template, using a function such as [`datasource`][] or [`include`][].
+Datasources can be defined with the [`--datasource`/`-d`][] command-line flag or the [`defineDatasource`][] function, and referenced via an _alias_ inside the template, using a function such as [`datasource`][] or [`include`][]. Datasources can additionally be loaded into the [context][] with the [`--context`/`-c`][] command-line flag.
Since datasources are defined separately from the template, the same templates can be used with different datasources and even different datasource types. For example, gomplate could be run on a developer machine with a `file` datasource pointing to a JSON file containing test data, where the same template could be used in a production environment using a `consul` datasource with the real production data.
@@ -432,6 +432,8 @@ $ gomplate -d vault=vault:///secret/foo -i '{{ (ds "vault").value }}'
The file `/tmp/vault-aws-nonce` will be created if it didn't already exist, and further executions of `gomplate` can re-authenticate securely.
[`--datasource`/`-d`]: ../usage/#datasource-d
+[`--context`/`-c`]: ../usage/#context-c
+[context]: ../syntax/#the-context
[`--datasource-header`/`-H`]: ../usage/#datasource-header-h
[`defineDatasource`]: ../functions/data/#definedatasource
[`datasource`]: ../functions/data/#datasource
diff --git a/docs/content/syntax.md b/docs/content/syntax.md
index 57eacda9..c736c41b 100644
--- a/docs/content/syntax.md
+++ b/docs/content/syntax.md
@@ -133,9 +133,10 @@ $ gomplate -i '{{ with "foo" }}The context is {{ . }}{{ end }}'
The context is foo
```
-Templates rendered by gomplate always have a _default_ context. In future, gomplate's
-context may expand (_watch this space!_), but currently, it contains one item: the
-system's environment variables, available as [`.Env`](#env).
+Templates rendered by gomplate always have a _default_ context. You can populate
+the default context from data sources with the [`--context`/`c`](../usage/#context-c)
+flag. The special context item [`.Env`](#env) is available for referencing the
+system's environment variables.
## Nested templates
diff --git a/docs/content/usage.md b/docs/content/usage.md
index 7e2dcdef..bdbc4249 100644
--- a/docs/content/usage.md
+++ b/docs/content/usage.md
@@ -77,6 +77,12 @@ A few different forms are valid:
- `mydata.json`
- This form infers the name from the file name (without extension). Only valid for files in the current directory.
+### `--context`/`c`
+
+Add a data source in `name=URL` form, and make it available in the [default context][] as `.<name>`. The special name `.` (period) can be used to override the entire default context.
+
+All other rules for the [`--datasource`/`-d`](#datasource-d) flag apply.
+
### Overriding the template delimiters
Sometimes it's necessary to override the default template delimiters (`{{`/`}}`).
diff --git a/gomplate.go b/gomplate.go
index 85bc8be6..da0ba097 100644
--- a/gomplate.go
+++ b/gomplate.go
@@ -26,6 +26,7 @@ type Config struct {
DataSources []string
DataSourceHeaders []string
+ Contexts []string
LDelim string
RDelim string
@@ -76,6 +77,9 @@ func (o *Config) String() string {
if len(o.DataSourceHeaders) > 0 {
c += "\ndatasourceheaders: " + strings.Join(o.DataSourceHeaders, ", ")
}
+ if len(o.Contexts) > 0 {
+ c += "\ncontexts: " + strings.Join(o.Contexts, ", ")
+ }
if o.LDelim != "{{" {
c += "\nleft_delim: " + o.LDelim
@@ -97,11 +101,11 @@ type gomplate struct {
rightDelim string
nestedTemplates templateAliases
rootTemplate *template.Template
+ context interface{}
}
// runTemplate -
func (g *gomplate) runTemplate(t *tplate) error {
- context := &context{}
tmpl, err := t.toGoTemplate(g)
if err != nil {
return err
@@ -114,19 +118,20 @@ func (g *gomplate) runTemplate(t *tplate) error {
defer t.target.(io.Closer).Close()
}
}
- err = tmpl.Execute(t.target, context)
+ err = tmpl.Execute(t.target, g.context)
return err
}
type templateAliases map[string]string
// newGomplate -
-func newGomplate(d *data.Data, leftDelim, rightDelim string, nested templateAliases) *gomplate {
+func newGomplate(d *data.Data, leftDelim, rightDelim string, nested templateAliases, context interface{}) *gomplate {
return &gomplate{
leftDelim: leftDelim,
rightDelim: rightDelim,
funcMap: Funcs(d),
nestedTemplates: nested,
+ context: context,
}
}
@@ -181,7 +186,8 @@ func parseTemplateArg(templateArg string, ta templateAliases) error {
func RunTemplates(o *Config) error {
Metrics = newMetrics()
defer runCleanupHooks()
- d, err := data.NewData(o.DataSources, o.DataSourceHeaders)
+ ds := append(o.DataSources, o.Contexts...)
+ d, err := data.NewData(ds, o.DataSourceHeaders)
if err != nil {
return err
}
@@ -190,7 +196,11 @@ func RunTemplates(o *Config) error {
if err != nil {
return err
}
- g := newGomplate(d, o.LDelim, o.RDelim, nested)
+ c, err := createContext(o.Contexts, d)
+ if err != nil {
+ return err
+ }
+ g := newGomplate(d, o.LDelim, o.RDelim, nested, c)
return g.runTemplates(o)
}
diff --git a/tests/integration/datasources_file_test.go b/tests/integration/datasources_file_test.go
index 017d1349..94b06d75 100644
--- a/tests/integration/datasources_file_test.go
+++ b/tests/integration/datasources_file_test.go
@@ -62,6 +62,18 @@ func (s *FileDatasourcesSuite) TestFileDatasources(c *C) {
result.Assert(c, icmd.Expected{ExitCode: 0, Out: "bar"})
result = icmd.RunCommand(GomplateBin,
+ "-c", "config="+s.tmpDir.Join("config2.yml"),
+ "-i", `{{ .config.foo}}`,
+ )
+ result.Assert(c, icmd.Expected{ExitCode: 0, Out: "bar"})
+
+ result = icmd.RunCommand(GomplateBin,
+ "-c", ".="+s.tmpDir.Join("config2.yml"),
+ "-i", `{{ .foo}} {{ (ds ".").foo }}`,
+ )
+ result.Assert(c, icmd.Expected{ExitCode: 0, Out: "bar bar"})
+
+ result = icmd.RunCommand(GomplateBin,
"-d", "config="+s.tmpDir.Join("config2.yml"),
"-i", `{{ if (datasourceReachable "bogus") }}bogus!{{ end -}}
{{ if (datasourceReachable "config") -}}