
templig
templig is configuration library utilizing the text templating engine and the functions best known from helm charts,
that originally stem from Masterminds/sprig.
Its primary goal is to enable access to the system environment to fill information using the env function. It also
enables to include verifications inside the configuration.
Usage
Simple Case
Having a configuration file like the following:
id: 23
name: Interesting Name
The code to read that file would look like this:
package main
import (
"fmt"
"github.com/AlphaOne1/templig"
)
type Config struct {
ID int `yaml:"id"`
Name string `yaml:"name"`
}
func main() {
c, confErr := templig.FromFile[Config]("my_config.yaml")
fmt.Printf("read errors: %v", confErr)
if confErr == nil {
fmt.Printf("ID: %v\n", c.Get().ID)
fmt.Printf("Name: %v\n", c.Get().Name)
}
}
The Get method gives a pointer to the internally held Config structure that the use supplied. The pinter is always
non-nil, so additional nil-checks are not necessary.
Reading environment
Having a templated configuration file like this one:
id: 23
name: Interesting Name
pass: {{ env "PASSWORD" | required "password required" | quote }}
or this one;
id: 23
name: Interesting Name
pass: {{ read "pass.txt" | required "password required" | quote }}
As demonstrated, one can use the templating functionality that is best known from helm charts. The functions provided
come from the aforementioned sprig-library. For convenience templig also
provides further functionality:
| Function |
Description |
| required |
checks that its second argument is not zero length or nil |
| read |
reads the content of a file |
package main
import (
"fmt"
"strings"
"github.com/AlphaOne1/templig"
)
type Config struct {
ID int `yaml:"id"`
Name string `yaml:"name"`
Pass string `yaml:"pass"`
}
func main() {
c, confErr := templig.FromFile[Config]("my_config.yaml")
fmt.Printf("read errors: %v", confErr)
if confErr == nil {
fmt.Printf("ID: %v\n", c.Get().ID)
fmt.Printf("Name: %v\n", c.Get().Name)
fmt.Printf("Pass: %v\n", strings.Repeat("*", len(c.Get().Pass)))
}
}
Validation
The templating facilities allow also for a wide range of tests, but depend on the configuration file read. As it is most
like user supplied, possible consistency checks are not reliable in the form of template code.
For this purpose, templig also allows for the configuration structure to implement the Validator interface.
Implementing types provide a function Validate that allows templig to check after the configuration was read, if
its structure could be considered valid and report errors accordingly.
package main
import (
"errors"
"fmt"
"github.com/AlphaOne1/templig"
)
type Config struct {
ID int `yaml:"id"`
Name string `yaml:"name"`
}
// Validate fulfills the Validator interface provided by templig.
// This method is called, if it is defined. It influences the outcome of the configuration reading.
func (c *Config) Validate() error {
result := make([]error, 0)
if len(c.Name) == 0 {
result = append(result, errors.New("name is required"))
}
if c.ID < 0 {
result = append(result, errors.New("id greater than zero is required"))
}
return errors.Join(result...)
}
func main() {
c, confErr := templig.FromFile[Config]("my_config_good.yaml")
if confErr == nil {
fmt.Printf("ID: %v\n", c.Get().ID)
fmt.Printf("Name: %v\n", c.Get().Name)
}
}
Validation functionality can be as simple as in this example. But as the complexity of the configuration grows,
automated tools to generate the configuration structure and basic consistency checks could be employed. These use
JSON Schema or its embedded form in OpenAPI 2 or 3.
A non-exhaustive list of these: