From b580bba092120362867f37a5a691dc8a1fd00d7c Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Thu, 8 Jun 2017 20:41:41 -0400 Subject: [PATCH 1/2] Namespacing the AWS funcs Signed-off-by: Dave Henderson --- aws/ec2info_test.go | 20 ------------- aws/testutils.go | 37 +++++++++++++++++++++++ funcs.go | 56 ++++++++++++++++++++++++++++++++++ funcs/aws.go | 73 +++++++++++++++++++++++++++++++++++++++++++++ funcs/aws_test.go | 21 +++++++++++++ gomplate.go | 51 +------------------------------ 6 files changed, 188 insertions(+), 70 deletions(-) create mode 100644 funcs.go create mode 100644 funcs/aws.go create mode 100644 funcs/aws_test.go diff --git a/aws/ec2info_test.go b/aws/ec2info_test.go index c99fe1615..3155b9c91 100644 --- a/aws/ec2info_test.go +++ b/aws/ec2info_test.go @@ -8,26 +8,6 @@ import ( "github.com/stretchr/testify/assert" ) -// test doubles -type DummyInstanceDescriber struct { - tags []*ec2.Tag -} - -func (d DummyInstanceDescriber) DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) { - output := &ec2.DescribeInstancesOutput{ - Reservations: []*ec2.Reservation{ - { - Instances: []*ec2.Instance{ - { - Tags: d.tags, - }, - }, - }, - }, - } - return output, nil -} - func TestTag_MissingKey(t *testing.T) { server, ec2meta := MockServer(200, `"i-1234"`) defer server.Close() diff --git a/aws/testutils.go b/aws/testutils.go index 32c8d4086..188dee7a5 100644 --- a/aws/testutils.go +++ b/aws/testutils.go @@ -5,6 +5,8 @@ import ( "net/http" "net/http/httptest" "net/url" + + "github.com/aws/aws-sdk-go/service/ec2" ) // MockServer - @@ -24,3 +26,38 @@ func MockServer(code int, body string) (*httptest.Server, *Ec2Meta) { client := &Ec2Meta{server.URL + "/", httpClient, false, make(map[string]string)} return server, client } + +// NewDummyEc2Info - +func NewDummyEc2Info(metaClient *Ec2Meta) *Ec2Info { + i := &Ec2Info{ + metaClient: metaClient, + describer: func() InstanceDescriber { return DummyInstanceDescriber{} }, + } + return i +} + +// NewDummyEc2Meta - +func NewDummyEc2Meta() *Ec2Meta { + return &Ec2Meta{nonAWS: true} +} + +// DummyInstanceDescriber - test doubles +type DummyInstanceDescriber struct { + tags []*ec2.Tag +} + +// DescribeInstances - +func (d DummyInstanceDescriber) DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) { + output := &ec2.DescribeInstancesOutput{ + Reservations: []*ec2.Reservation{ + { + Instances: []*ec2.Instance{ + { + Tags: d.tags, + }, + }, + }, + }, + } + return output, nil +} diff --git a/funcs.go b/funcs.go new file mode 100644 index 000000000..ac7900909 --- /dev/null +++ b/funcs.go @@ -0,0 +1,56 @@ +package main + +import ( + "net/url" + "strings" + "text/template" + + "github.com/hairyhenderson/gomplate/funcs" +) + +// initFuncs - The function mappings are defined here! +func initFuncs(data *Data) template.FuncMap { + env := &Env{} + typeconv := &TypeConv{} + stringfunc := &stringFunc{} + + f := template.FuncMap{ + "getenv": env.Getenv, + "bool": typeconv.Bool, + "has": typeconv.Has, + "json": typeconv.JSON, + "jsonArray": typeconv.JSONArray, + "yaml": typeconv.YAML, + "yamlArray": typeconv.YAMLArray, + "toml": typeconv.TOML, + "csv": typeconv.CSV, + "csvByRow": typeconv.CSVByRow, + "csvByColumn": typeconv.CSVByColumn, + "slice": typeconv.Slice, + "indent": typeconv.indent, + "join": typeconv.Join, + "toJSON": typeconv.ToJSON, + "toJSONPretty": typeconv.toJSONPretty, + "toYAML": typeconv.ToYAML, + "toTOML": typeconv.ToTOML, + "toCSV": typeconv.ToCSV, + "contains": strings.Contains, + "hasPrefix": strings.HasPrefix, + "hasSuffix": strings.HasSuffix, + "replaceAll": stringfunc.replaceAll, + "split": strings.Split, + "splitN": strings.SplitN, + "title": strings.Title, + "toUpper": strings.ToUpper, + "toLower": strings.ToLower, + "trim": strings.Trim, + "trimSpace": strings.TrimSpace, + "urlParse": url.Parse, + "datasource": data.Datasource, + "ds": data.Datasource, + "datasourceExists": data.DatasourceExists, + "include": data.include, + } + funcs.AWSFuncs(f) + return f +} diff --git a/funcs/aws.go b/funcs/aws.go new file mode 100644 index 000000000..695922beb --- /dev/null +++ b/funcs/aws.go @@ -0,0 +1,73 @@ +package funcs + +import ( + "sync" + + "github.com/hairyhenderson/gomplate/aws" +) + +var ( + af *Funcs + afInit sync.Once +) + +// AWSNS - the aws namespace +func AWSNS() *Funcs { + afInit.Do(func() { af = &Funcs{} }) + return af +} + +// AWSFuncs - +func AWSFuncs(f map[string]interface{}) { + f["aws"] = AWSNS + + // global aliases - for backwards compatibility + f["ec2meta"] = AWSNS().EC2Meta + f["ec2dynamic"] = AWSNS().EC2Dynamic + f["ec2tag"] = AWSNS().EC2Tag + f["ec2region"] = AWSNS().EC2Region +} + +// Funcs - +type Funcs struct { + meta *aws.Ec2Meta + metaInit sync.Once + info *aws.Ec2Info + infoInit sync.Once +} + +// EC2Region - +func (a *Funcs) EC2Region(def ...string) string { + a.metaInit.Do(a.initMeta) + return a.meta.Region(def...) +} + +// EC2Meta - +func (a *Funcs) EC2Meta(key string, def ...string) string { + a.metaInit.Do(a.initMeta) + return a.meta.Meta(key, def...) +} + +// EC2Dynamic - +func (a *Funcs) EC2Dynamic(key string, def ...string) string { + a.metaInit.Do(a.initMeta) + return a.meta.Dynamic(key, def...) +} + +// EC2Tag - +func (a *Funcs) EC2Tag(tag string, def ...string) string { + a.infoInit.Do(a.initInfo) + return a.info.Tag(tag, def...) +} + +func (a *Funcs) initMeta() { + if a.meta == nil { + a.meta = aws.NewEc2Meta() + } +} + +func (a *Funcs) initInfo() { + if a.info == nil { + a.info = aws.NewEc2Info() + } +} diff --git a/funcs/aws_test.go b/funcs/aws_test.go new file mode 100644 index 000000000..4d79d7c24 --- /dev/null +++ b/funcs/aws_test.go @@ -0,0 +1,21 @@ +package funcs + +import ( + "testing" + + "github.com/hairyhenderson/gomplate/aws" + "github.com/stretchr/testify/assert" +) + +func TestNSIsIdempotent(t *testing.T) { + assert.True(t, AWSNS() == AWSNS()) +} +func TestFuncs(t *testing.T) { + m := aws.NewDummyEc2Meta() + i := aws.NewDummyEc2Info(m) + af := &Funcs{meta: m, info: i} + assert.Equal(t, "unknown", af.EC2Region()) + assert.Equal(t, "", af.EC2Meta("foo")) + assert.Equal(t, "", af.EC2Tag("foo")) + assert.Equal(t, "unknown", af.EC2Region()) +} diff --git a/gomplate.go b/gomplate.go index f02c6f146..71530368b 100644 --- a/gomplate.go +++ b/gomplate.go @@ -3,11 +3,7 @@ package main import ( "io" "log" - "net/url" - "strings" "text/template" - - "github.com/hairyhenderson/gomplate/aws" ) func (g *Gomplate) createTemplate() *template.Template { @@ -36,55 +32,10 @@ func (g *Gomplate) RunTemplate(text string, out io.Writer) { // NewGomplate - func NewGomplate(data *Data, leftDelim, rightDelim string) *Gomplate { - env := &Env{} - typeconv := &TypeConv{} - stringfunc := &stringFunc{} - ec2meta := aws.NewEc2Meta() - ec2info := aws.NewEc2Info() return &Gomplate{ leftDelim: leftDelim, rightDelim: rightDelim, - funcMap: template.FuncMap{ - "getenv": env.Getenv, - "bool": typeconv.Bool, - "has": typeconv.Has, - "json": typeconv.JSON, - "jsonArray": typeconv.JSONArray, - "yaml": typeconv.YAML, - "yamlArray": typeconv.YAMLArray, - "toml": typeconv.TOML, - "csv": typeconv.CSV, - "csvByRow": typeconv.CSVByRow, - "csvByColumn": typeconv.CSVByColumn, - "slice": typeconv.Slice, - "indent": typeconv.indent, - "join": typeconv.Join, - "toJSON": typeconv.ToJSON, - "toJSONPretty": typeconv.toJSONPretty, - "toYAML": typeconv.ToYAML, - "toTOML": typeconv.ToTOML, - "toCSV": typeconv.ToCSV, - "ec2meta": ec2meta.Meta, - "ec2dynamic": ec2meta.Dynamic, - "ec2tag": ec2info.Tag, - "ec2region": ec2meta.Region, - "contains": strings.Contains, - "hasPrefix": strings.HasPrefix, - "hasSuffix": strings.HasSuffix, - "replaceAll": stringfunc.replaceAll, - "split": strings.Split, - "splitN": strings.SplitN, - "title": strings.Title, - "toUpper": strings.ToUpper, - "toLower": strings.ToLower, - "trim": strings.Trim, - "trimSpace": strings.TrimSpace, - "urlParse": url.Parse, - "datasource": data.Datasource, - "ds": data.Datasource, - "datasourceExists": data.DatasourceExists, - "include": data.include, - }, + funcMap: initFuncs(data), } } From d8f2a9362b0427d4031054abc4b9971a95dba82d Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Thu, 8 Jun 2017 20:42:18 -0400 Subject: [PATCH 2/2] Updating docs to split functions by namespace Signed-off-by: Dave Henderson --- docs/config.toml | 31 -------- docs/content/functions/aws.md | 75 +++++++++++++++++++ .../{functions.md => functions/general.md} | 70 +---------------- docs/content/index.md | 5 +- docs/content/installing.md | 1 + docs/content/syntax.md | 9 ++- docs/content/usage.md | 3 +- 7 files changed, 94 insertions(+), 100 deletions(-) create mode 100644 docs/content/functions/aws.md rename docs/content/{functions.md => functions/general.md} (89%) diff --git a/docs/config.toml b/docs/config.toml index f1484cc98..3277cc091 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -4,7 +4,6 @@ title = "gomplate documentation" theme = "hugo-material-docs" - [params] author = "hairyhenderson" description = "gomplate documentation" @@ -19,33 +18,3 @@ theme = "hugo-material-docs" [social] twitter = "hairyhenderson" github = "hairyhenderson" - -[[menu.main]] - name = "About" - url = "/" - weight = 1 - pre = "" - -[[menu.main]] - name = "Installing" - url = "installing/" - weight = 10 - pre = "" - -[[menu.main]] - name = "Usage" - url = "usage/" - weight = 11 - pre = "" - -[[menu.main]] - name = "Syntax" - url = "syntax/" - weight = 12 - pre = "" - -[[menu.main]] - name = "Built-in Functions" - url = "functions/" - weight = 13 - pre = "" diff --git a/docs/content/functions/aws.md b/docs/content/functions/aws.md new file mode 100644 index 000000000..d5aa235d9 --- /dev/null +++ b/docs/content/functions/aws.md @@ -0,0 +1,75 @@ +--- +title: aws functions +menu: + main: + parent: functions +--- + + +## `aws.EC2Meta` + +**Alias:** _(to be deprecated)_ `ec2meta` + +Queries AWS [EC2 Instance Metadata](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) for information. This only retrieves data in the `meta-data` path -- for data in the `dynamic` path use `aws.EC2Dynamic`. + +This only works when running `gomplate` on an EC2 instance. If the EC2 instance metadata API isn't available, the tool will timeout and fail. + +#### Example + +```console +$ echo '{{aws.EC2Meta "instance-id"}}' | gomplate +i-12345678 +``` + +## `aws.EC2Dynamic` + +**Alias:** _(to be deprecated)_ `ec2dynamic` + +Queries AWS [EC2 Instance Dynamic Metadata](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) for information. This only retrieves data in the `dynamic` path -- for data in the `meta-data` path use `aws.EC2Meta`. + +This only works when running `gomplate` on an EC2 instance. If the EC2 instance metadata API isn't available, the tool will timeout and fail. + +#### Example + +```console +$ echo '{{ (aws.EC2Dynamic "instance-identity/document" | json).region }}' | ./gomplate +us-east-1 +``` + +## `aws.EC2Region` + +**Alias:** _(to be deprecated)_ `ec2region` + +Queries AWS to get the region. An optional default can be provided, or returns +`unknown` if it can't be determined for some reason. + +#### Example + +_In EC2_ +```console +$ echo '{{ aws.EC2Region }}' | ./gomplate +us-east-1 +``` +_Not in EC2_ +```console +$ echo '{{ aws.EC2Region }}' | ./gomplate +unknown +$ echo '{{ aws.EC2Region "foo" }}' | ./gomplate +foo +``` + +## `aws.EC2Tag` + +**Alias:** _(to be deprecated)_ `ec2tag` + +Queries the AWS EC2 API to find the value of the given [user-defined tag](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html). An optional default +can be provided. + +#### Example + +```console +$ echo 'This server is in the {{ aws.EC2Tag "Account" }} account.' | ./gomplate +foo +$ echo 'I am a {{ aws.EC2Tag "classification" "meat popsicle" }}.' | ./gomplate +I am a meat popsicle. +``` \ No newline at end of file diff --git a/docs/content/functions.md b/docs/content/functions/general.md similarity index 89% rename from docs/content/functions.md rename to docs/content/functions/general.md index 2cf6a6c76..28f48009b 100644 --- a/docs/content/functions.md +++ b/docs/content/functions/general.md @@ -1,12 +1,10 @@ --- -title: Built-in functions -weight: 0 +title: other functions +menu: + main: + parent: functions --- -In addition to all of the functions and operators that the [Go template](https://golang.org/pkg/text/template/) -language provides (`if`, `else`, `eq`, `and`, `or`, `range`, etc...), there are -some additional functions baked in to `gomplate`: - ## `contains` Contains reports whether the second string is contained within the first. Equivalent to @@ -814,63 +812,3 @@ $ gomplate -d person.json -f input.tmpl ] } ``` - -## `ec2meta` - -Queries AWS [EC2 Instance Metadata](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) for information. This only retrieves data in the `meta-data` path -- for data in the `dynamic` path use `ec2dynamic`. - -This only works when running `gomplate` on an EC2 instance. If the EC2 instance metadata API isn't available, the tool will timeout and fail. - -#### Example - -```console -$ echo '{{ec2meta "instance-id"}}' | gomplate -i-12345678 -``` - -## `ec2dynamic` - -Queries AWS [EC2 Instance Dynamic Metadata](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) for information. This only retrieves data in the `dynamic` path -- for data in the `meta-data` path use `ec2meta`. - -This only works when running `gomplate` on an EC2 instance. If the EC2 instance metadata API isn't available, the tool will timeout and fail. - -#### Example - -```console -$ echo '{{ (ec2dynamic "instance-identity/document" | json).region }}' | ./gomplate -us-east-1 -``` - -## `ec2region` - -Queries AWS to get the region. An optional default can be provided, or returns -`unknown` if it can't be determined for some reason. - -#### Example - -_In EC2_ -```console -$ echo '{{ ec2region }}' | ./gomplate -us-east-1 -``` -_Not in EC2_ -```console -$ echo '{{ ec2region }}' | ./gomplate -unknown -$ echo '{{ ec2region "foo" }}' | ./gomplate -foo -``` - -## `ec2tag` - -Queries the AWS EC2 API to find the value of the given [user-defined tag](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html). An optional default -can be provided. - -#### Example - -```console -$ echo 'This server is in the {{ ec2tag "Account" }} account.' | ./gomplate -foo -$ echo 'I am a {{ ec2tag "classification" "meat popsicle" }}.' | ./gomplate -I am a meat popsicle. -``` diff --git a/docs/content/index.md b/docs/content/index.md index 2ad6fee69..244d475d9 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -1,7 +1,10 @@ --- title: gomplate type: index -weight: 0 +weight: 1 +menu: + main: + name: About --- A [Go template](https://golang.org/pkg/text/template/)-based CLI tool. `gomplate` can be used as an alternative to diff --git a/docs/content/installing.md b/docs/content/installing.md index b9812ef7e..ba5666810 100644 --- a/docs/content/installing.md +++ b/docs/content/installing.md @@ -1,6 +1,7 @@ --- title: Installing weight: 10 +menu: main --- # Installing diff --git a/docs/content/syntax.md b/docs/content/syntax.md index 0b092087f..e68af3b5b 100644 --- a/docs/content/syntax.md +++ b/docs/content/syntax.md @@ -1,6 +1,7 @@ --- title: Syntax -weight: 0 +weight: 12 +menu: main --- ## About `.Env` @@ -13,3 +14,9 @@ Sometimes, this behaviour is desired; if the output is unusable without certain strings, this is a sure way to know that variables are missing! If you want different behaviour, try [`getenv`](../functions/#getenv). + +## Built-in functions + +In addition to all of the functions and operators that the [Go template](https://golang.org/pkg/text/template/) +language provides (`if`, `else`, `eq`, `and`, `or`, `range`, etc...), there are +some additional functions baked in to `gomplate`. See the links on the left. \ No newline at end of file diff --git a/docs/content/usage.md b/docs/content/usage.md index a0b665a5c..3fdd67319 100644 --- a/docs/content/usage.md +++ b/docs/content/usage.md @@ -1,6 +1,7 @@ --- title: Usage -weight: 0 +weight: 11 +menu: main --- The simplest usage of `gomplate` is to just replace environment