Skip to content

Commit 926c2ad

Browse files
authored
feat!: use expr language for executable templates and request transformations (#204)
1 parent 4606279 commit 926c2ad

31 files changed

+637
-187
lines changed

docs/guide/conditional.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ using a simple expression language that provides access to system information, e
55

66
### Expression Language
77

8-
Flow uses the [Expr](https://expr-lang.org) language for evaluating conditions. The language supports common
8+
flow uses the [Expr](https://expr-lang.org) language for evaluating conditions. The language supports common
99
operators and functions while providing access to flow executable-specific context data.
1010

1111
**See the [Expr language documentation](https://expr-lang.org/docs/language-definition) for more information on the

docs/guide/executable.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,9 @@ executables:
365365
url: "http://pi.hole/admin/api.php?disable=$DURATION&auth=$PWHASH"
366366
logResponse: true # log the response body
367367
validStatusCodes: [200] # only consider the execution successful if the status code is 200
368-
# transform the response body with jq
368+
# transform the response body with a Expr expression
369369
transformResponse: |
370-
if .status == "disabled" then .status = "pause" else . end
370+
"paused: " + string(fromJSON(body)["status"] == "disabled")
371371
```
372372
373373
##### render

docs/guide/templating.md

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -100,18 +100,18 @@ files will only be copied if the `Type` field is set to `Helm`. The `deploy.sh`
100100
artifacts:
101101
- srcName: "helm-deploy.sh"
102102
srcDir: "scripts" # By default, the file will be copied from the template directory. This field can be used to specify a different directory.
103-
if: "{{ eq .Type 'Helm' }}" # This will only copy the file if the Helm field is true
103+
if: form["Type"] == "Helm" # This will only copy the file if the Helm field is true
104104
- srcName: "deploy.sh"
105105
srcDir: "scripts"
106-
if: "{{ eq .Type 'K8s' }}" # This will only copy the file if the K8s field is true
106+
if: form["Type"] == "K8s" # This will only copy the file if the K8s field is true
107107
- srcName: "values.yaml.tmpl"
108108
asTemplate: true
109109
dstName: "values.yaml"
110-
if: "{{ eq .Type 'Helm' }}" # This will only copy the file if the Helm field is true
110+
if: form["Type"] == "Helm" # This will only copy the file if the Helm field is true
111111
- srcName: "resources.yaml.tmpl"
112112
asTemplate: true
113113
dstName: "resources.yaml"
114-
if: "{{ eq .Type 'K8s' }}" # This will only copy the file if the K8s field is true
114+
if: form["Type"] == "K8s" # This will only copy the file if the K8s field is true
115115
```
116116

117117
### flowfile template string
@@ -126,22 +126,22 @@ template: |
126126
tags: [k8s]
127127
executables:
128128
- verb: deploy
129-
name: "{{ .FlowFileName }}"
129+
name: "{{ name }}"
130130
exec:
131-
file: "{{ if eq .Type 'Helm' }}helm-deploy.sh{{ else }}deploy.sh{{ end }}"
131+
file: "{{ if form["Type"] == 'Helm' }}helm-deploy.sh{{ else }}deploy.sh{{ end }}"
132132
params:
133133
- envKey: "NAMESPACE"
134-
text: "{{ .Namespace }}"
134+
text: "{{ form["Namespace"] }}"
135135
- envKey: "APP_NAME"
136-
text: "{{ .FlowFileName }}"
136+
text: "{{ name }}"
137137
- verb: restart
138-
name: "{{ .FlowFileName }}"
138+
name: "{{ name }}"
139139
exec:
140-
cmd: "kubectl rollout restart deployment/{{ .FlowFileName }} -n {{ .Namespace }}"
140+
cmd: "kubectl rollout restart deployment/{{ name }} -n {{ form["Namespace"] }}"
141141
- verb: open
142-
name: "{{ .FlowFileName }}"
142+
name: "{{ name }}"
143143
launch:
144-
uri: "https://{{ .FlowFileName }}.my.haus"
144+
uri: "https://{{ name }}.my.haus"
145145
```
146146

147147
### Pre- and post- run executables
@@ -156,14 +156,15 @@ Before exiting, it will also run a simple command and either open the flowfile i
156156
preRun:
157157
- ref: "validate k8s/validation:context" # You can reference other executables that you have on your system
158158
args: ["homelab"]
159-
if: "{{ .Deploy }}"
159+
if: form["Deploy"]
160160
postRun:
161-
- cmd: "echo 'Rendered {{ if .Helm }}Helm values{{ else }}k8s manifest{{end}}'; ls -al"
161+
- cmd: |
162+
echo 'Rendered {{ if form["Helm"] }}Helm values{{ else }}k8s manifest{{end}}'; ls -al
162163
- ref: "edit vscode"
163-
args: ["{{ .FlowFilePath }}"]
164-
if: "{{ not .Deploy }}"
165-
- ref: "deploy {{ .FlowFileName }}"
166-
if: "{{ .Deploy }}"
164+
args: ["{{ flowFilePath }}"]
165+
if: not form["Deploy"]
166+
- ref: "deploy {{ name }}"
167+
if: form["Deploy"]
167168
```
168169

169170
**Note**: preRun executables are run from the template directory, while postRun executables are run from the output directory.
@@ -215,36 +216,56 @@ postRun:
215216
- ref: "edit vscode"
216217
args: ["{{ .FlowFilePath }}"]
217218
if: "{{ not .Deploy }}"
218-
- ref: "deploy {{ .FlowFileName }}"
219+
- ref: "deploy {{ .name }}"
219220
if: "{{ .Deploy }}"
220221
template: |
221222
tags: [k8s]
222223
executables:
223224
- verb: deploy
224-
name: "{{ .FlowFileName }}"
225+
name: "{{ name }}"
225226
exec:
226227
file: "{{ if eq .Type 'Helm' }}helm-deploy.sh{{ else }}deploy.sh{{ end }}"
227228
params:
228229
- envKey: "NAMESPACE"
229-
text: "{{ .Namespace }}"
230+
text: "{{ .form.namespace }}"
230231
- envKey: "APP_NAME"
231-
text: "{{ .FlowFileName }}"
232+
text: "{{ name }}"
232233
- verb: restart
233-
name: "{{ .FlowFileName }}"
234+
name: "{{ name }}"
234235
exec:
235236
cmd: "kubectl rollout restart deployment/{{ .FlowFileName }} -n {{ .Namespace }}"
236237
- verb: open
237-
name: "{{ .FlowFileName }}"
238+
name: "{{ name }}"
238239
launch:
239-
uri: "https://{{ .FlowFileName }}.my.haus"
240-
```
240+
uri: "https://{{ name }}.my.haus"
241+
```
242+
### Templating language
243+
244+
flow uses a hybrid of [Go text templating](https://pkg.go.dev/text/template) and [Expr](https://expr-lang.org) language for
245+
rendering templates.
246+
247+
Aside from the `if` field in the artifacts, preRun, and postRun sections, all other fields that use templating
248+
will need to include the `{{` and `}}` delimiters to indicate that the text should be rendered as a template.
249+
250+
**Template Variables**
251+
252+
The following variables are automatically available in all template expressions:
241253

242-
### Template helpers
254+
| Variable | Type | Description |
255+
|-----------------|------------------------|------------------------------------------------------------------|
256+
| `os` | string | Operating system identifier (e.g., "linux", "darwin", "windows") |
257+
| `arch` | string | System architecture (e.g., "amd64", "arm64") |
258+
| `workspace` | string | Target workspace name |
259+
| `workspacePath` | string | Full path to the target workspace root directory |
260+
| `name` | string | Name provided for the newly rendered flow file |
261+
| `directory` | string | Target directory that the template will be render in to |
262+
| `flowFilePath` | string | Full path to the target flow file |
263+
| `templatePath` | string | Path to the template file being rendered |
264+
| `env` | map (string -> string) | Environment variables accessible to the template |
265+
| `form` | map (string -> any) | Values provided through template form inputs |
243266

244-
Templates can use the [Sprig functions](https://masterminds.github.io/sprig/) to manipulate data during the rendering process.
245-
Additionally, the following keys are available to the template:
267+
**See the [Expr language documentation](https://expr-lang.org/docs/language-definition) for more information on the
268+
additional expression functions and syntax.**
246269

247-
- `FlowFileName`: The name of the flowfile being rendered. This is the argument passed into the `generate` command.
248-
- `FlowFilePath`: The output path to the flowfile being rendered.
249-
- `FlowWorkspace`: The name of the workspace that the template is rendering into.
250-
- `FlowWorkspacePath`: The path to the workspace root directory.
270+
> [!NOTE]
271+
> The `env` map contains environment variables that were present when the template was rendered. The `form` map contains values from any form inputs defined in the template configuration.

docs/schemas/flowfile_schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@
390390
"default": "30m0s"
391391
},
392392
"transformResponse": {
393-
"description": "JQ query to transform the response before saving it to a file or outputting it.",
393+
"description": "[Expr](https://expr-lang.org/docs/language-definition) expression used to transform the response before \nsaving it to a file or outputting it.\n\nThe following variables are available in the expression:\n - `status`: The response status string.\n - `code`: The response status code.\n - `body`: The response body.\n - `headers`: The response headers.\n\nFor example, to capitalize a JSON body field's value, you can use `upper(fromJSON(body)[\"field\"])`.\n",
394394
"type": "string",
395395
"default": ""
396396
},

docs/schemas/template_schema.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"default": ""
3131
},
3232
"if": {
33-
"description": "A condition to determine if the artifact should be copied. The condition is evaluated using Go templating \nfrom the form data. If the condition is not met, the artifact will not be copied.\n[Sprig functions](https://masterminds.github.io/sprig/) are available for use in the condition.\n\nFor example, to copy the artifact only if the `name` field is set:\n```\n{{ if .name }}true{{ end }}\n```\n",
33+
"description": "An expression that determines whether the the artifact should be copied, using the Expr language syntax. \nThe expression is evaluated at runtime and must resolve to a boolean value. If the condition is not met, \nthe artifact will not be copied.\n\nThe expression has access to OS/architecture information (os, arch), environment variables (env), form input \n(form), and context information (name, workspace, directory, etc.).\n\nSee the [flow documentation](https://flowexec.io/#/guide/templating) for more information.\n",
3434
"type": "string",
3535
"default": ""
3636
},
@@ -121,7 +121,7 @@
121121
"default": ""
122122
},
123123
"if": {
124-
"description": "A condition to determine if the executable should be run. The condition is evaluated using Go templating \nfrom the form data. If the condition is not met, the executable run will be skipped.\n[Sprig functions](https://masterminds.github.io/sprig/) are available for use in the condition.\n\nFor example, to run a command only if the `name` field is set:\n```\n{{ if .name }}true{{ end }}\n```\n",
124+
"description": "An expression that determines whether the executable should be run, using the Expr language syntax. \nThe expression is evaluated at runtime and must resolve to a boolean value. If the condition is not met, \nthe executable will be skipped.\n\nThe expression has access to OS/architecture information (os, arch), environment variables (env), form input \n(form), and context information (name, workspace, directory, etc.).\n\nSee the [flow documentation](https://flowexec.io/#/guide/templating) for more information.\n",
125125
"type": "string",
126126
"default": ""
127127
},

docs/types/flowfile.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ Makes an HTTP request.
302302
| `params` | | [ExecutableParameterList](#ExecutableParameterList) | <no value> | |
303303
| `responseFile` | | [ExecutableRequestResponseFile](#ExecutableRequestResponseFile) | <no value> | |
304304
| `timeout` | The timeout for the request in Go duration format (e.g. 30s, 5m, 1h). | `string` | 30m0s | |
305-
| `transformResponse` | JQ query to transform the response before saving it to a file or outputting it. | `string` | | |
305+
| `transformResponse` | [Expr](https://expr-lang.org/docs/language-definition) expression used to transform the response before saving it to a file or outputting it. The following variables are available in the expression: - `status`: The response status string. - `code`: The response status code. - `body`: The response body. - `headers`: The response headers. For example, to capitalize a JSON body field's value, you can use `upper(fromJSON(body)["field"])`. | `string` | | |
306306
| `url` | The URL to make the request to. | `string` | ||
307307
| `validStatusCodes` | A list of valid status codes. If the response status code is not in this list, the executable will fail. If not set, the response status code will not be checked. | `array` (`integer`) | [] | |
308308

docs/types/template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Go templating from form data is supported in all fields.
3939
| `asTemplate` | If true, the artifact will be copied as a template file. The file will be rendered using Go templating from the form data. [Sprig functions](https://masterminds.github.io/sprig/) are available for use in the template. | `boolean` | false | |
4040
| `dstDir` | The directory to copy the file to. If not set, the file will be copied to the root of the flow file directory. The directory will be created if it does not exist. | `string` | | |
4141
| `dstName` | The name of the file to copy to. If not set, the file will be copied with the same name. | `string` | | |
42-
| `if` | A condition to determine if the artifact should be copied. The condition is evaluated using Go templating from the form data. If the condition is not met, the artifact will not be copied. [Sprig functions](https://masterminds.github.io/sprig/) are available for use in the condition. For example, to copy the artifact only if the `name` field is set: ``` {{ if .name }}true{{ end }} ``` | `string` | | |
42+
| `if` | An expression that determines whether the the artifact should be copied, using the Expr language syntax. The expression is evaluated at runtime and must resolve to a boolean value. If the condition is not met, the artifact will not be copied. The expression has access to OS/architecture information (os, arch), environment variables (env), form input (form), and context information (name, workspace, directory, etc.). See the [flow documentation](https://flowexec.io/#/guide/templating) for more information. | `string` | | |
4343
| `srcDir` | The directory to copy the file from. If not set, the file will be copied from the directory of the template file. | `string` | | |
4444
| `srcName` | The name of the file to copy. | `string` | <no value> | |
4545

@@ -94,7 +94,7 @@ Configuration for a template executable.
9494
| ----- | ----------- | ---- | ------- | :--------: |
9595
| `args` | Arguments to pass to the executable. | `array` (`string`) | [] | |
9696
| `cmd` | The command to execute. One of `cmd` or `ref` must be set. | `string` | | |
97-
| `if` | A condition to determine if the executable should be run. The condition is evaluated using Go templating from the form data. If the condition is not met, the executable run will be skipped. [Sprig functions](https://masterminds.github.io/sprig/) are available for use in the condition. For example, to run a command only if the `name` field is set: ``` {{ if .name }}true{{ end }} ``` | `string` | | |
97+
| `if` | An expression that determines whether the executable should be run, using the Expr language syntax. The expression is evaluated at runtime and must resolve to a boolean value. If the condition is not met, the executable will be skipped. The expression has access to OS/architecture information (os, arch), environment variables (env), form input (form), and context information (name, workspace, directory, etc.). See the [flow documentation](https://flowexec.io/#/guide/templating) for more information. | `string` | | |
9898
| `ref` | A reference to another executable to run in serial. One of `cmd` or `ref` must be set. | [ExecutableRef](#ExecutableRef) | | |
9999

100100

examples/exec-template.flow.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ template: |
1010
namespace: examples
1111
executables:
1212
- verb: run
13-
name: "{{ .Color }}-msg"
14-
description: Created from a template in {{ .FlowWorkspace }}
13+
name: {{ form["Color"] }}-msg
14+
description: Created from a template in {{ workspace }}
1515
exec:
1616
cmd: cat message.txt

examples/request.flow

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ executables:
4141
url: https://httpbin.org/post
4242
body: '{"hello": "world"}'
4343
logResponse: true
44-
transformResponse: .args.hello = "universe" | .args
44+
transformResponse: |
45+
"Hello, " + upper(fromJSON(body)["json"]["hello"])
4546

4647
- verb: send
4748
name: param-request

go.mod

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ require (
1010
github.com/charmbracelet/lipgloss v1.0.0
1111
github.com/expr-lang/expr v1.16.9
1212
github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4
13-
github.com/itchyny/gojq v0.12.17
1413
github.com/jahvon/glamour v0.8.1-patch3
1514
github.com/jahvon/open-golang v0.0.0-20240522004812-68511c3bc9ef
1615
github.com/jahvon/tuikit v0.0.27
@@ -58,7 +57,6 @@ require (
5857
github.com/gorilla/css v1.0.1 // indirect
5958
github.com/huandu/xstrings v1.5.0 // indirect
6059
github.com/inconshreveable/mousetrap v1.1.0 // indirect
61-
github.com/itchyny/timefmt-go v0.1.6 // indirect
6260
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
6361
github.com/mattn/go-isatty v0.0.20 // indirect
6462
github.com/mattn/go-localereader v0.0.1 // indirect

0 commit comments

Comments
 (0)