Skip to content

Commit

Permalink
basic functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
niima committed Jun 23, 2022
1 parent c0c06b6 commit 71ed8a9
Show file tree
Hide file tree
Showing 10 changed files with 343 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*.dll
*.so
*.dylib
gotlet

# Test binary, built with `go test -c`
*.test
Expand Down
136 changes: 136 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,138 @@
# gotlet
Simple Templating command line tool using go template engine

## Instalation
```
chmod +x ./gotlet
mv ./gotlet /usr/local/bin/gotlet
```

## Example usage:

```
./gotlet -t deployment.yaml -d data.yaml -v
```

## Flags:
- `-t` Specify the template file path (required)
- `-d` Specify the data file path containing the variables in Yaml (optional, if not specified you have only environment variables to include)
- `-p` A prefix for filtering which env variables to include
- `-o` Output file path (default: result.yaml)
- `-v` Print out the result in stdout

## Template Engine Reference:
To use a variable, eighter from environment variables or the specified use the following syntax which is standard go template syntax.

```
statictext {{ .variable_name }} static text
```
Nested variables:
```
statictext {{ .variable_name.sub_var_name }} static text
```
Environment variables:
```
statictext {{ .USER }} static text
```
Iterate in a key-value dictionary
```
env:
{{range $key, $value := .environment_variables}}
- name: {{ $key }}
value: {{ $value }}
{{end}}
```

Read more at [official go documents](https://pkg.go.dev/text/template)

## Sample
This is a basic variable file:
> Note that variables root element is required and MUST be `variables`
```yaml
variables:
service_name: nginx
version: 2
component: front-end
port: 80
frontend_max_replicas: 3
frontend_image: "nginx:latest"
environment_variables:
environment: production
api_url: "https://api.url.com"
project_name: website
```
This is a sample template file which is a kubernetes deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{.service_name}}-{{.component}}-{{.version}}
labels:
app: {{.service_name}}
component: {{.component}}
version: {{.version}}
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 2
maxSurge: {{ .frontend_max_replicas }}
spec:
containers:
- name: {{.service_name}}
image: {{.frontend_image}}
imagePullPolicy: IfNotPresent
ports:
- containerPort: {{.ops_port}}
env: {{range $key, $value := .environment_variables}}
- name: {{ $key }}
value: {{ $value }} {{end}}

```

running this command would generate this file:
```
./gotlet -t ./examples/template.yaml -d ./examples/variables.yaml -o export.yaml
```
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-front-end-2
labels:
app: nginx
component: front-end
version: 2
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 2
maxSurge: 3
spec:
containers:
- name: nginx
image: nginx:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: <no value>
env:
- name: api_url
value: https://api.url.com
- name: environment
value: production
- name: project_name
value: website

```

## Build from source
To run on your local machine (you need go 1.18+ installed)
```
go build .
```

To run on a linux server or a linux pipeline runner:
```
GOOS=linux GOARCH=amd64 go build -o gotlet-amd64-linux main.go
```
24 changes: 24 additions & 0 deletions examples/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{.service_name}}-{{.component}}-{{.version}}
labels:
app: {{.service_name}}
component: {{.component}}
version: {{.version}}
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 2
maxSurge: {{ .frontend_max_replicas }}
spec:
containers:
- name: {{.service_name}}
image: {{.frontend_image}}
imagePullPolicy: IfNotPresent
ports:
- containerPort: {{.ops_port}}
env: {{range $key, $value := .environment_variables}}
- name: {{ $key }}
value: {{ $value }} {{end}}
11 changes: 11 additions & 0 deletions examples/variables.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
variables:
service_name: nginx
version: 2
component: front-end
port: 80
frontend_max_replicas: 3
frontend_image: "nginx:latest"
environment_variables:
environment: production
api_url: "https://api.url.com"
project_name: website
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module gotlet

go 1.18

require gopkg.in/yaml.v3 v3.0.1
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
63 changes: 63 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package main

import (
"flag"
"fmt"
"gotlet/pkg/colors"
"io/ioutil"
"os"
)

func main() {
if len(flag.Args()) == 0 {
fmt.Println("gotlet renders your variables into a template file.")
}

templateFile := flag.String("t", "", "Specify the template file path")
envPrefix := flag.String("p", "", "A prefix for filtering which env variables to include")
dataFile := flag.String("d", "", "Specify the data file containing the variables in Yaml")
outputPath := flag.String("o", "result.yaml", "Output file path")
printOutput := flag.Bool("v", false, "Print out the result in stdout")

flag.Parse()

if *templateFile == "" {
fmt.Println(colors.Red + "- The template file path is required, use -t flag")
os.Exit(1)
}

if *envPrefix == "" {
fmt.Println(colors.Yellow, "- No env variable prefix specified, loading all env variables.\n"+
"\tThis is not recommended, use -p flag to specify", colors.Reset)
}

data := &Model{
Variables: make(map[string]any),
}

err := getVariables(data, *dataFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

addEnvVariables(data, *envPrefix)

result, err := renderTemplate(*templateFile, data)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

if *outputPath != "" {
err = ioutil.WriteFile(*outputPath, result, 0644)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

if *printOutput {
fmt.Println(string(result))
}
}
11 changes: 11 additions & 0 deletions pkg/colors/colors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package colors

var Reset = "\033[0m"
var Red = "\033[31m"
var Green = "\033[32m"
var Yellow = "\033[33m"
var Blue = "\033[34m"
var Purple = "\033[35m"
var Cyan = "\033[36m"
var Gray = "\033[37m"
var White = "\033[97m"
33 changes: 33 additions & 0 deletions render.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"bytes"
"io/ioutil"
"text/template"
)

type Model struct {
Variables map[string]any
}

func renderTemplate(templateFile string, data *Model) ([]byte, error) {
originalContent, err := ioutil.ReadFile(templateFile)
if err != nil {
return nil, err
}

tmpl := template.New(templateFile)

t, err := tmpl.Parse(string(originalContent))
if err != nil {
return nil, err
}

renderedContent := new(bytes.Buffer)
err = t.Execute(renderedContent, data.Variables)
if err != nil {
return nil, err
}

return renderedContent.Bytes(), nil
}
55 changes: 55 additions & 0 deletions variables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main

import (
"fmt"
"io/ioutil"
"os"
"strings"

"gopkg.in/yaml.v3"
)

func getVariables(data *Model, variablesFile string) error {
if variablesFile != "" {
err := readVariables(data, variablesFile)
if err != nil {
return err
}
}

return nil
}

func addEnvVariables(data *Model, envPrefix string) {
envs := envToMap()
for k, v := range envs {
if strings.HasPrefix(k, envPrefix) {
data.Variables[k] = v
}
}
}

func readVariables(m *Model, variables string) error {
yamlFile, err := ioutil.ReadFile(variables)
if err != nil {
return err
}

err = yaml.Unmarshal(yamlFile, m)
if err != nil {
return fmt.Errorf("yamlFile.Unmarshal error #%w ", err)
}

return nil
}

func envToMap() map[string]string {
envMap := make(map[string]string)

for _, v := range os.Environ() {
splitV := strings.SplitN(v, "=", 2)
envMap[splitV[0]] = splitV[1]
}

return envMap
}

0 comments on commit 71ed8a9

Please sign in to comment.