Skip to content

Commit

Permalink
Merge pull request #32 from patrickdappollonio/required-support
Browse files Browse the repository at this point in the history
Add support for required.
  • Loading branch information
patrickdappollonio authored Aug 24, 2023
2 parents 80a2b1d + 71dd19c commit f889590
Show file tree
Hide file tree
Showing 16 changed files with 370 additions and 138 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/pr-flow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Continuous Integration - PR
on:
pull_request:

jobs:
test-app:
name: Test Application
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.21
- name: Test application
run: go test ./...
- name: Dry-run goreleaser application
uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser
version: latest
args: release --snapshot --skip-publish --clean
- name: Create temporary download for this PR for 1d
uses: actions/upload-artifact@v3
with:
name: downloads
path: dist/
if-no-files-found: error
retention-days: 1
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml → .github/workflows/push-flow.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
name: Continuous Integration
on:
push:
pull_request:

jobs:
test-app:
name: Test Application
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/[email protected]
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.19
go-version: 1.21
- name: Test application
run: go test ./...
- name: Compile application
Expand Down
12 changes: 9 additions & 3 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ builds:
ldflags:
- -s -w -X main.version={{.Version}} -extldflags "-static"
archives:
- replacements:
386: i386
amd64: x86_64
- id: foo
name_template: >-
{{- .ProjectName }}_
{{- .Version }}_
{{- .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end -}}
checksum:
name_template: 'checksums.txt'
snapshot:
Expand Down
129 changes: 32 additions & 97 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

[![Downloads](https://img.shields.io/github/downloads/patrickdappollonio/tgen/total?color=blue&logo=github&style=flat-square)](https://github.com/patrickdappollonio/tgen/releases)


`tgen` is a simple CLI application that allows you to write a template file and then use the power of Go Templates to generate an output (which is) outputted to `stdout`. Besides the Go Template engine itself, `tgen` contains a few extra utility functions to assist you when writing templates. See below for a description of each.

You can also use the `--help` (or `-h`) to see the available set of options. The only flag required is the file to process, and everything else is optional.
Expand All @@ -15,7 +14,7 @@ Usage:
Flags:
-e, --environment string an optional environment file to use (key=value formatted) to perform replacements
-f, --file string the template file to process
-f, --file string the template file to process, or "-" to read from stdin
-d, --delimiter string template delimiter (default "{{}}")
-x, --execute string a raw template to execute directly, without providing --file
-v, --values string a file containing values to use for the template, a la Helm
Expand All @@ -25,132 +24,68 @@ Flags:
--version version for tgen
```

### Environment file
## Usage

`tgen` supports an optional environment variable collection in a file but it's a pretty basic implementation of a simple key/value pair. The environment file works by finding lines that aren't empty or preceded by a pound `#` -- since they're treated as comments -- and then tries to find at least one equal (`=`) sign. If it can find at least one, all values on the left side of the equal sign become the key and the contents on the right side become the value. If the same line has more than one equal, only the first one is honored and all remaining ones become part of the value.
You can use `tgen`:

There's no support for Bash interpolation or multiline values. If this is needed, consider using a YAML values file instead.
* By reading environment variables from the environment or a key-value file
* By reading variables from a YAML values file

#### Example
While working with it, `tgen` supports a "strict" mode, where if a variable (either environment or from a values file) is used in the template but not set, it will fail the template generation.

Consider the following template, named `template.txt`:
## Examples

```handlebars
The dog licked the {{ env "element" }} and everyone laughed.
```
### Simple template

And the following environment file, named `contents.env`:
Using a template file and an environment file, you can generate a template as follows:

```bash
element=Oil
```
$ cat template.txt
The dog licked the {{ env "element" }} and everyone laughed.

After being passed to `tgen`, the output becomes:
$ cat contents.env
element=Oil

```bash
$ tgen -e contents.env -f template.txt
The dog licked the Oil and everyone laughed.
```

Using the inline mode to execute a template, you can also call the program as such (note the use of single-quotes since in Go, strings are always double-quoted) which will yield the same result:

```bash
$ tgen -x '{{ env "element" }}' -e contents.env
The dog licked the Oil and everyone laughed.
```

Do note as well that using single quotes for the template allows you to prevent any bash special parsing logic that your terminal might have.

### Helm-style values

`tgen` can be used to generate templates, in a very similar way as `helm` can be used. However, do note that `tgen`'s intention is not to replace `helm` since it can't handle application lifecycle the way `helm` does, however, it can do a great job generating resources with very similar code.

Consider the following example of creating a Kubernetes secret for a `tls.crt` file -- in real environments, you'll also need the key, but for the sake of this example, it has been omitted.

Checking the files in the folder:

```bash
tree .
```

```text
.
├── secret.yaml
└── tls.crt
0 directories, 2 files
```
### Inline mode

We have a `secret.yaml` which includes `tgen` templating notation:
You can skip the template file altogether and use the inline mode to execute a template directly:

```bash
cat secret.yaml
```
$ cat contents.env
element=Oil

```yaml
apiVersion: v1
kind: Secret
metadata:
name: secret-tls
type: kubernetes.io/tls
data:
tls.crt: | {{ readfile "tls.crt" | b64enc | nindent 4 }}
$ tgen -e contents.env -x 'The dog licked the {{ env "element" }} and everyone laughed.'
The dog licked the Oil and everyone laughed.
```

The last line includes the following logic:
* Reads the `tls.crt` file from the current directory where `tgen` is run
* Takes the contents of the file and converts it to `base64` -- required by Kubernetes secrets
* Then indents with 4 spaces, starting with a new line
### Helm-style values

To generate the output, we can now run `tgen`:
While `tgen` Helm-like support is currently limited to values, it allows for a powerful way to generate templates. Consider the following example:

```bash
tgen -f secret.yaml
```

And the output looks like this:

```yaml
apiVersion: v1
kind: Secret
metadata:
name: secret-tls
type: kubernetes.io/tls
data:
tls.crt: |
Rk9PQkFSQkFaCg==
```
$ cat template.txt
The dog licked the {{ .element }} and everyone laughed.

This output can be then passed to Kubernetes as follows:
$ cat values.yaml
element: Oil

```
tgen -f secret.yaml | kubectl apply -f -
```

Do keep in mind though your DevOps requirements in terms of keeping a copy of your YAML files, rendered. Additionally, the `readfile` function is akin to `helm`'s `.Files`, with the exception that **you can read any file the `tgen` binary has access**, including potentially sensitive files such as `/etc/passwd`. If this is a concern, please run `tgen` in a CI/CD environment or where access to these resources is limited.

You can also use a `values.yaml` file like Helm. `tgen` will allow you to read values from the values file as `.variable` or `.Values.variable`. The latter is the same as Helm's `.Values.variable` and the former is a shortcut to `.Values.variable` for convenience. Consider the following YAML values file:

```yaml
name: Patrick
```

And the following template:

```handlebars
Hello, my name is {{ .name }}.
$ tgen -v values.yaml -f template.txt
The dog licked the Oil and everyone laughed.
```

Running `tgen` with the values file will yield the following output:
In the last function call, if your file is named `values.yaml`, you can omit it calling it directly and instead use:

```bash
$ tgen -f template.yaml -v values.yaml
Hello, my name is Patrick.
$ tgen --with-values -f template.txt
The dog licked the Oil and everyone laughed.
```

If your values file is called `values.yaml`, you also have the handy shortcut of simply specifying `--with-values` and `tgen` will automatically include the values file from the current working directory.
For more details, see the ["Kubernetes and Helm-style values" documentation page](docs/helm-style-values.md).

### Template functions
## Template functions

See [template functions](docs/functions.md) for a list of all the functions available.
4 changes: 2 additions & 2 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ func command(w io.Writer, c conf) error {

// Read template from "-x" or "--execute" flag
if c.stdinTemplateFile != "" {
tg.setTemplate("-", c.stdinTemplateFile)
tg.setTemplate(os.Stdin.Name(), c.stdinTemplateFile)
}

// Read template file (either from "--file" or stdin)
if pathToOpen := c.templateFilePath; pathToOpen != "" {
var err error
switch pathToOpen {
case "-":
err = tg.loadTemplateFile("", os.Stdin)
err = tg.loadTemplateFile(os.Stdin.Name(), os.Stdin)
default:
err = tg.loadTemplatePath(pathToOpen)
}
Expand Down
31 changes: 28 additions & 3 deletions docs/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [`readfile`, `readlocalfile`](#readfile-readlocalfile)
- [`linebyline`, `lbl`](#linebyline-lbl)
- [`after`, `skip`](#after-skip)
- [`required`](#required)

All examples below have been generated using `-x` -- or `--execute`, which allows passing a template as argument rather than reading a file. In either case, whether the template file -- with `-f` or `--file` -- or the template argument is used, all functions are available.

Expand Down Expand Up @@ -91,6 +92,8 @@ Both `env` and `envdefault` are case insensitive -- either `"home"` or `"HOME"`

When `--strict` mode is enabled, if `env` is called with a environment variable name with no value set or set to empty, the application will exit with error. Useful if you must receive a value or fail a CI build, for example.

Consider the following example reading these environment variables:

```bash
$ tgen -x '{{ env "user" }}'
patrick
Expand All @@ -99,16 +102,22 @@ $ tgen -x '{{ env "USER" }}'
patrick
```

Then trying to read a nonexistent environment variable with `--strict` mode enabled:

```bash
$ tgen -x '{{ env "foobar" }}' -s
Error: strict mode on: environment variable not found: $FOOBAR
$ tgen -x '{{ env "foobar" }}' --strict
Error: evaluating /dev/stdin:1:3: strict mode on: environment variable not found: $FOOBAR
```

And bypassing strict mode by setting a default value:

```bash
$ tgen -x '{{ envdefault "SQL_HOST" "sql.example.com" }}'
$ tgen -x '{{ envdefault "SQL_HOST" "sql.example.com" }}' --strict
sql.example.com
```

For custom messages, [consider using `required` instead](#required).

### `rndstring`

Generates a random string of a given length:
Expand Down Expand Up @@ -199,3 +208,19 @@ $ tgen -x '{{ after 2 (seq 5) }}'
$ tgen -x '{{ seq 5 | after 2 }}'
[3 4 5]
```

### `required`

Returns an error if the value is empty. Useful to ensure a value is provided, and if not, fail the template generation.

```bash
$ tgen -x '{{ env "foo" | required "environment variable \"foo\" is required" }}'
Error: evaluating /dev/stdin:1:15: environment variable "foo" is required
```

Note that you can also use `--strict` mode to achieve a similar result. The difference between `--strict` and `required` is that `required` works anywhere: not just on missing YAML value keys or environment variables. Here's another example:

```bash
$ tgen -x '{{ "" | required "Value must be set" }}'
Error: evaluating /dev/stdin:1:8: Value must be set
```
Loading

0 comments on commit f889590

Please sign in to comment.