How to concatenate Service metadata for consul-template with commas - templates

Does anyone know how to concatenate strings from consul for consul-template?
If I have a service 'foo' registered in Consul
{
"Node": "node1",
"Address": "192.168.0.1",
"Port": 3333
},
{
"Node": "node2",
"Address": "192.168.0.2",
"Port": 4444
}
I would like consul-template to generate the following line:
servers=192.168.0.1:3333,192.168.0.2:4444/bogus
The following attempt does not work since it leaves a trailing comma ,
servers={{range service "foo"}}{{.Address}}{{.Port}},{{end}}/bogus
# renders
servers=192.168.0.1:3333,192.168.0.2:4444,/bogus
# What I actually want
servers=192.168.0.1:3333,192.168.0.2:4444/bogus
I know consul-template uses golang template syntax, but I simply cannot figure out the syntax to get this working. Its likely that I should use consul-template's join but how do I pass both .Address and .Port to join? This is just a trivial example, and I'm not using indexes intentionally since the number of services could be more than two. Any ideas?

This should work.
{{$foo_srv := service "foo"}}
{{if $foo_srv}}
{{$last := len $foo_srv | subtract 1}}
servers=
{{- range $i := loop $last}}
{{- with index $foo_srv $i}}{{.Address}}{{.Port}},{{end}}
{{- end}}
{{- with index $foo_srv last}}{{.Address}}{{.Port}}{{end}}/bogus
{{end}}
I was thinking if "join" can be used.
Note "{{-" means removing leading white spaces (such ' ', \t, \n).

You can use a custom plugin.
servers={{service "foo" | toJSON | plugin "path/to/plugin"}}
The plugin code:
package main
import (
"encoding/json"
"fmt"
"os"
)
type InputEntry struct {
Node string
Address string
Port int
}
func main() {
arg := []byte(os.Args[1])
var input []InputEntry
if err := json.Unmarshal(arg, &input); err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("err: %s", err))
os.Exit(1)
}
var output string
for i, entry := range input {
output += fmt.Sprintf("%v:%v", entry.Address, entry.Port)
if i != len(input)-1 {
output += ","
}
}
fmt.Fprintln(os.Stdout, string(output))
os.Exit(0)
}

Related

Escape special characters while sending email through AWS ses

Hello I am using AWS SES SDK V2 to send emails. I have a case where sender name is like Michael čisto. If I pass this name directly to AWS email input struct then it gives me following error:
operation error SESv2: SendEmail, https response error StatusCode: 400, BadRequestException: Missing '"'
Here is my code:
package main
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sesv2"
"github.com/aws/aws-sdk-go-v2/service/sesv2/types"
)
func main() {
name := "Michael čisto"
body := "test email body"
subject := "test email"
CharSet := "UTF-8"
/* Assemble the email */
input := &sesv2.SendEmailInput{
Destination: &types.Destination{
CcAddresses: []string{},
ToAddresses: []string{receiverEmail},
},
Content: &types.EmailContent{
Simple: &types.Message{
Body: &types.Body{
Html: &types.Content{
Charset: aws.String(CharSet),
Data: aws.String(body),
},
},
Subject: &types.Content{
Charset: aws.String(CharSet),
Data: aws.String(subject),
},
},
},
ReplyToAddresses: []string{"\"" + name + "\" <" + senderEmail + ">"},
FromEmailAddress: aws.String("\"" + name + "\" <" + senderEmail + ">"),
}
cfg, err := awsconfig.LoadDefaultConfig(context.TODO(),
awsconfig.WithRegion("us-east-1"),
)
client := sesv2.NewFromConfig(cfg)
emailResp, err = client.SendEmail(context.TODO(), input)
}
Can anyone help me to figure out how to escape these kind of characters in GO ?
Try to format the address using mail.Address.String:
package main
import (
"fmt"
"net/mail"
)
func main() {
a := mail.Address{Name: "Michael čisto", Address: "sender#example.com"}
fmt.Println(a.String())
// Output:
// =?utf-8?q?Michael_=C4=8Disto?= <sender#example.com>
}
In case your domain contains non-ASCII characters:
Please note that Amazon SES does not support the SMTPUTF8 extension. If the domain part of an address (the part after the # sign) contains non-ASCII characters, they must be encoded using Punycode (see types.BulkEmailEntry). mail.Address.String does not do this for you. But you can use idna.ToASCII to convert the domain yourself.

Saving HTML to a golang template variables

I am using go-lang templates to output some HTML. There is a block of HTML that I will like to repeat many times. So I am using a variable to store this block of HTML. Here is a dummy version of my code:
package main
import (
"html/template"
"log"
"os"
)
var tmplString = ` // content of index.html
{{define "index"}}
{{ $DUMMY := "{{.var1}} is equal to {{.var2}}" }}
{{ $DUMMY }}
{{ $DUMMY }}
{{end}}
`
func main() {
tmpl, err := template.New("test").Parse(tmplString)
if err != nil {
log.Fatal(err)
}
varmap := map[string]interface{}{
"var1": "value",
"var2": 100,
}
tmpl.ExecuteTemplate(os.Stdout, "index", varmap)
}
The problem is that this piece of code will
{{.var1}} is equal to {{.var2}}
{{.var1}} is equal to {{.var2}}
But I need it to produce
value is equal to 100
value is equal to 100
How can I define a variable so that the string gets built from my structure?
You can concatenate strings in templates with print:
{{ $DUMMY := (print .var1 " is equal to " .var2 )}}

route53 list hosted zones output throws "does not support indexing" error

I can't parse the output of the response below.
When I include the line:
"fmt.Println(*r["HostedZones"][0])"
it throws:
"type *route53.ListHostedZonesOutput does not support indexing".
I'd like to retrieve the "Id" and "Name" of each zone in the output. If the type doesn't support indexing, how can I retrieve the parts of the output I need?
Thank you.
package main
import (
"log"
"fmt"
"reflect"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/route53"
)
func main() {
r53 := route53.New(session.New())
r, err := r53.ListHostedZones(&route53.ListHostedZonesInput{})
if err != nil {
log.Fatal(err)
}
log.Println(r)
fmt.Println(reflect.TypeOf(r))
fmt.Println(*r["HostedZones"][0])
}
sample output:
{
HostedZones: [{
CallerReference: "5E95CADD-59E-A6",
Config: {
PrivateZone: false
},
Id: "/hostedzone/Z1Q1TZTO",
Name: "testzone.local.",
ResourceRecordSetCount: 4
},{
CallerReference: "39895A3C-9B8B-95C2A3",
Config: {
PrivateZone: false
},
Id: "/hostedzone/Z2MXJQ7",
Name: "2.168.192.in-addr.arpa.",
ResourceRecordSetCount: 3
}],
IsTruncated: false,
MaxItems: "100"
}
This is an example of how to get the Id:
fmt.Println(*r.HostedZones[0].Id)
Name:
fmt.Println(*r.HostedZones[0].Name)
you are probably trying to index with the pointer to your struct, your r is a pointer, you can get the deferenced value by doing (*r)["yourkeyhere"]["index"].

Simplify template usage by removing array

I am trying to simplify the template which I use to make it use a flatter data structure:
from
data := []App{App{"test data", []string{"app1", "app2", "app3"}}}
To:
data := App{App{"test data", []string{"app1", "app2", "app3"}}}
i.e. Remove the array of App, but when I try it I get an error.
Here is the working version: https://play.golang.org/p/2THGtDvlu01
I tried to change the template to
{{ range . -}}
{range $i,$a := .Command}{{if gt $i 0 }} && {{end}}{{.}}{{end}}
{{end}}
But I got an error of type mismatched, any idea how to fix it?
package main
import (
"log"
"os"
"text/template"
)
func main() {
// Define a template.
const tmpl = `
echo &1
{{range $i,$a := .Command}}{{if gt $i 0 }} && {{end}}{{.}}{{end}}
echo 2
`
// Prepare some data
type App struct {
Data string
Command []string
}
data := App{"test data", []string{"app1", "app2", "app3"}}
// Create a new template and parse into it.
t := template.Must(template.New("tmpl").Parse(tmpl))
// Execute the template with data
err := t.Execute(os.Stdout, data)
if err != nil {
log.Println("executing template:", err)
}
}
Playground example
Gives the output
echo &1
app1 && app2 && app3
echo 2
Program exited.
If you remove the []App from your code, you also need to remove the range used in the template.

How to print the value of a key containing dots

I'm trying to print the values of a map, whose keys have a dot (.) on it.
Example map:
type TemplateData struct {
Data map[string] int
}
tpldata := TemplateData{map[string]int {"core.value": 1}}
I've tried:
{{ $key := "core.value" }}
{{ .Data.key }}
but got:
2014/06/17 16:46:17 http: panic serving [::1]:41395: template: template.html:13: bad character U+0024 '$'
and
{{ .Data."core.value" }}
but got:
2014/06/17 16:45:07 http: panic serving [::1]:41393: template: template.html:12: bad character U+0022 '"'
Note that I'm able to successfully print the value of keys without dots.
As #martin-ghallager said, one needs to use an external function to access those elements.
Helpfully, the standard library already provides the index function (which does exactly what Martin's dotNotation function does).
To use it just write:
{{ index .Data "core.value" }}
The index function will return a default value in case the key is not present. This works if your dictionary has homogeneous data, however it will return the wrong value when it is heterogeneous. In such a case you can explicitly set the default with:
{{ 0 | or (index .Data "core.value") }}
As fabrizioM has stated, it's against the specs of the package, however there's nothing stopping you creating your own accessor to use dot notation using a function map:
package main
import (
"fmt"
"html/template"
"os"
)
type TemplateData struct {
Data map[string]int
}
var funcMap = template.FuncMap{
"dotNotation": dotNotation,
}
func main() {
data := TemplateData{map[string]int{"core.value": 1, "test": 100}}
t, err := template.New("foo").Funcs(funcMap).Parse(`{{dotNotation .Data "core.value"}}`)
if err != nil {
fmt.Println(err)
}
err = t.Execute(os.Stdout, data)
if err != nil {
fmt.Println(err)
}
}
func dotNotation(m map[string]int, key string) int {
// Obviously you'll need to validate existence / nil map
return m[key]
}
http://play.golang.org/p/-rlKFx3Ayt
No you can't.
According to the specs in http://golang.org/pkg/text/template/#Arguments, the key must be alphanumeric
- The name of a key of the data, which must be a map, preceded
by a period, such as
.Key
The result is the map element value indexed by the key.
Key invocations may be chained and combined with fields to any
depth:
.Field1.Key1.Field2.Key2
Although the key must be an alphanumeric identifier, unlike with
field names they do not need to start with an upper case letter.
Keys can also be evaluated on variables, including chaining:
$x.key1.key2
You can still print it by iterating over the Map
package main
import (
"fmt"
"html/template"
"os"
)
type TemplateData struct {
Data map[string]int
}
func main() {
data := TemplateData{map[string]int{"core.value": 1, "test": 100}}
t, err := template.New("foo").Parse(`{{range $key, $value := .Data}}
{{$key}}: {{$value}}
{{end}}`)
if err != nil {
fmt.Println(err)
}
err = t.Execute(os.Stdout, data)
if err != nil {
fmt.Println(err)
}
}
http://play.golang.org/p/6xB_7WQ-59
I had a similar issue where my key names in secret vault had - and . in it for example
test-key or test.key
If solved it like this
{{ with secret "secret/path/test"}}
{{ range $k, $v := .Data }}
{{ $k }}:{{ $v }}
{{ end }}
{{ end }}
Hope this will help someone...
Similarly for vault templates when exporting secrets as envvars this worked for me:
{{- with secret "secret/path/mysecret" -}}
export MYVAR="{{ index .Data.data "mykey"}}"
{{- end }}