Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ homebrew_casks:
name: homebrew-tap
token: "{{ .Env.HOMEBREW_FLOW_GITHUB_TOKEN }}"
completions:
bash: completions/flow.bash
zsh: completions/flow.zsh
fish: completions/flow.fish
bash: scripts/completions/flow.bash
zsh: scripts/completions/flow.zsh
fish: scripts/completions/flow.fish
# dependencies:
# - cask: xclip # Required for clipboard support, only linux, so I need to figure out to get this to skip macOS
hooks:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ flow run hello
flow complements existing CLI tools by adding multi-project organization, built-in security, and visual discovery to your automation toolkit.

- **Workspace organization** - Group and manage workflows across multiple projects
- **Encrypted secret vaults** - Multiple backends (AES, Age, external tools)
- **Encrypted secret vaults** - Multiple backends (AES, Age, keyring, external tools)
- **Interactive discovery** - Browse, search, and filter workflows visually
- **Flexible execution** - Serial, parallel, conditional, and interactive workflows
- **Workflow generation** - Create projects and workflows from reusable templates
Expand Down
2 changes: 1 addition & 1 deletion cmd/internal/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func cacheSetFunc(ctx *context.Context, _ *cobra.Command, args []string) {
if err != nil {
logger.Log().FatalErr(err)
}
if err = form.Run(ctx.Ctx); err != nil {
if err = form.Run(ctx); err != nil {
logger.Log().FatalErr(err)
}
value = form.FindByKey("value").Value()
Expand Down
2 changes: 1 addition & 1 deletion cmd/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func resetConfigFunc(ctx *context.Context, _ *cobra.Command, _ []string) {
if err != nil {
logger.Log().FatalErr(err)
}
if err := form.Run(ctx.Ctx); err != nil {
if err := form.Run(ctx); err != nil {
logger.Log().FatalErr(err)
}
resp := form.FindByKey("confirm").Value()
Expand Down
6 changes: 3 additions & 3 deletions cmd/internal/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func execFunc(ctx *context.Context, cmd *cobra.Command, verb executable.Verb, ar
if err != nil {
logger.Log().FatalErr(err)
}
if err := form.Run(ctx.Ctx); err != nil {
if err := form.Run(ctx); err != nil {
logger.Log().FatalErr(err)
}
for key, val := range form.ValueMap() {
Expand Down Expand Up @@ -223,7 +223,7 @@ func runByRef(ctx *context.Context, cmd *cobra.Command, argsStr string) error {
execCmd.SetIn(ctx.StdIn())
execPreRun(ctx, execCmd, []string{id})
execFunc(ctx, execCmd, verb, []string{id})
ctx.CancelFunc()
ctx.Cancel()
return nil
}

Expand All @@ -241,7 +241,7 @@ func setAuthEnv(ctx *context.Context, _ *cobra.Command, executable *executable.E
if err != nil {
logger.Log().FatalErr(err)
}
if err := form.Run(ctx.Ctx); err != nil {
if err := form.Run(ctx); err != nil {
logger.Log().FatalErr(err)
}
val := form.FindByKey(vault.EncryptionKeyEnvVar).Value()
Expand Down
10 changes: 9 additions & 1 deletion cmd/internal/flags/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ var VaultSetFlag = &Metadata{
var VaultTypeFlag = &Metadata{
Name: "type",
Shorthand: "t",
Usage: "Vault type. Either age or aes256",
Usage: "Vault type. Either unencrypted, age, aes256, keyring, or external",
Default: "aes256",
Required: false,
}
Expand Down Expand Up @@ -250,3 +250,11 @@ var VaultIdentityFileFlag = &Metadata{
Default: "",
Required: false,
}

var VaultFromFileFlag = &Metadata{
Name: "config",
Shorthand: "c",
Usage: "File path to read the external vault's configuration from. The file must be a valid vault configuration file.",
Default: "",
Required: false,
}
4 changes: 2 additions & 2 deletions cmd/internal/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func removeSecretFunc(ctx *context.Context, _ *cobra.Command, args []string) {
if err != nil {
logger.Log().FatalErr(err)
}
if err := form.Run(ctx.Ctx); err != nil {
if err := form.Run(ctx); err != nil {
logger.Log().FatalErr(err)
}
resp := form.FindByKey("confirm").Value()
Expand Down Expand Up @@ -137,7 +137,7 @@ func setSecretFunc(ctx *context.Context, cmd *cobra.Command, args []string) {
if err != nil {
logger.Log().FatalErr(err)
}
if err := form.Run(ctx.Ctx); err != nil {
if err := form.Run(ctx); err != nil {
logger.Log().FatalErr(err)
}
value = form.FindByKey("value").Value()
Expand Down
18 changes: 16 additions & 2 deletions cmd/internal/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func registerCreateVaultCmd(ctx *context.Context, vaultCmd *cobra.Command) {
RegisterFlag(ctx, createCmd, *flags.VaultTypeFlag)
RegisterFlag(ctx, createCmd, *flags.VaultPathFlag)
RegisterFlag(ctx, createCmd, *flags.VaultSetFlag)
RegisterFlag(ctx, createCmd, *flags.VaultFromFileFlag)
// AES flags
RegisterFlag(ctx, createCmd, *flags.VaultKeyEnvFlag)
RegisterFlag(ctx, createCmd, *flags.VaultKeyFileFlag)
Expand All @@ -82,6 +83,8 @@ func createVaultFunc(ctx *context.Context, cmd *cobra.Command, args []string) {
setVault := flags.ValueFor[bool](cmd, *flags.VaultSetFlag, false)

switch strings.ToLower(vaultType) {
case "unencrypted":
vaultV2.NewUnencryptedVault(vaultName, vaultPath)
case "aes256":
keyEnv := flags.ValueFor[string](cmd, *flags.VaultKeyEnvFlag, false)
keyFile := flags.ValueFor[string](cmd, *flags.VaultKeyFileFlag, false)
Expand All @@ -92,8 +95,19 @@ func createVaultFunc(ctx *context.Context, cmd *cobra.Command, args []string) {
identityEnv := flags.ValueFor[string](cmd, *flags.VaultIdentityEnvFlag, false)
identityFile := flags.ValueFor[string](cmd, *flags.VaultIdentityFileFlag, false)
vaultV2.NewAgeVault(vaultName, vaultPath, recipients, identityEnv, identityFile)
case "keyring":
vaultV2.NewKeyringVault(vaultName)
case "external":
cfgFile := flags.ValueFor[string](cmd, *flags.VaultFromFileFlag, false)
if cfgFile == "" {
logger.Log().Fatalf("external vault requires a configuration file to be specified with --config")
}
vaultV2.NewExternalVault(vaultPath)
default:
logger.Log().Fatalf("unsupported vault type: %s - must be one of 'aes256' or 'age'", vaultType)
logger.Log().Fatalf(
"unsupported vault type: %s - must be one of 'aes256', 'age', 'unencrypted', 'keyring', or 'external'",
vaultType,
)
}

if ctx.Config.Vaults == nil {
Expand Down Expand Up @@ -229,7 +243,7 @@ func removeVaultFunc(ctx *context.Context, _ *cobra.Command, args []string) {
if err != nil {
logger.Log().FatalErr(err)
}
if err := form.Run(ctx.Ctx); err != nil {
if err := form.Run(ctx); err != nil {
logger.Log().FatalErr(err)
}
resp := form.FindByKey("confirm").Value()
Expand Down
2 changes: 1 addition & 1 deletion cmd/internal/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func removeWorkspaceFunc(ctx *context.Context, _ *cobra.Command, args []string)
if err != nil {
logger.Log().FatalErr(err)
}
if err := form.Run(ctx.Ctx); err != nil {
if err := form.Run(ctx); err != nil {
logger.Log().FatalErr(err)
}
resp := form.FindByKey("confirm").Value()
Expand Down
4 changes: 2 additions & 2 deletions desktop/src-tauri/src/types/generated/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub mod error {
#[doc = " ],"]
#[doc = " \"properties\": {"]
#[doc = " \"asTemplate\": {"]
#[doc = " \"description\": \"If true, the artifact will be copied as a template file. The file will be rendered using Go templating from \\nthe form data. [Sprig functions](https://masterminds.github.io/sprig/) are available for use in the template.\\n\","]
#[doc = " \"description\": \"If true, the artifact will be copied as a template file. The file will be rendered using Go templating from \\nthe form data. [Expr language functions](https://expr-lang.org/docs/language-definition) are available for use in the template.\\n\","]
#[doc = " \"default\": false,"]
#[doc = " \"type\": \"boolean\""]
#[doc = " },"]
Expand Down Expand Up @@ -76,7 +76,7 @@ pub mod error {
#[doc = r" </details>"]
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
pub struct Artifact {
#[doc = "If true, the artifact will be copied as a template file. The file will be rendered using Go templating from \nthe form data. [Sprig functions](https://masterminds.github.io/sprig/) are available for use in the template.\n"]
#[doc = "If true, the artifact will be copied as a template file. The file will be rendered using Go templating from \nthe form data. [Expr language functions](https://expr-lang.org/docs/language-definition) are available for use in the template.\n"]
#[serde(rename = "asTemplate", default)]
pub as_template: bool,
#[doc = "The directory to copy the file to. If not set, the file will be copied to the root of the flow file directory.\nThe directory will be created if it does not exist.\n"]
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/types/generated/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface Template {
export interface Artifact {
/**
* 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.
* the form data. [Expr language functions](https://expr-lang.org/docs/language-definition) are available for use in the template.
*
*/
asTemplate?: boolean;
Expand Down
3 changes: 2 additions & 1 deletion docs/cli/flow_vault_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ flow vault create NAME [flags]
### Options

```
-c, --config string File path to read the external vault's configuration from. The file must be a valid vault configuration file.
-h, --help help for create
--identity-env string Environment variable name for the Age vault identity. Only used for Age vaults.
--identity-file string File path for the Age vault identity. An absolute path is recommended. Only used for Age vaults.
Expand All @@ -17,7 +18,7 @@ flow vault create NAME [flags]
-p, --path string Directory that the vault will use to store its data. If not set, the vault will be stored in the flow cache directory.
--recipients string Comma-separated list of recipient keys for the vault. Only used for Age vaults.
-s, --set Set the newly created vault as the current vault
-t, --type string Vault type. Either age or aes256 (default "aes256")
-t, --type string Vault type. Either unencrypted, age, aes256, keyring, or external (default "aes256")
```

### Options inherited from parent commands
Expand Down
36 changes: 36 additions & 0 deletions docs/guide/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ The expression language supports standard comparison and logical operators:
- String: `+` (concatenation), `matches` (regex matching)
- Length: `len()`

**See the [Expr Language Definition](https://expr-lang.org/docs/language-definition) for all available operators and functions.**

### Basic Conditions <!-- {docsify-ignore} -->

Use the `if` field to control when executables run:
Expand Down Expand Up @@ -61,6 +63,27 @@ executables:
cmd: npm test
```

### File System Conditions <!-- {docsify-ignore} -->
Check for files or directories to control execution:

```yaml
executables:
- verb: deploy
name: app
serial:
execs:
# Only run if config file exists
- if: fileExists("config.yaml")
cmd: kubectl apply -f config.yaml

# Abort deployment if no Dockerfile found
- if: not fileExists("Dockerfile")
cmd: echo "No Dockerfile found"; exit 1

# Run deployment if Dockerfile exists
- cmd: docker build -t myapp .
```

### Data-Driven Conditions <!-- {docsify-ignore} -->

Use stored data to control execution flow:
Expand Down Expand Up @@ -104,6 +127,19 @@ Conditions have access to extensive runtime information:
- `ctx.flowFilePath` - Path to current flow file
- `ctx.flowFileDir` - Directory containing current flow file

Additionally the following functions are provided alongside the Expr language definition:

- `fileExists(path)` - Check if file/directory exists
- `dirExists(path)` - Check if path is a directory
- `isFile(path)` - Check if path is a file
- `isDir(path)` - Check if path is a directory
- `basename(path)` - Get filename from path
- `dirname(path)` - Get directory from path
- `readFile(path)` - Read file contents as string
- `fileSize(path)` - Get file size in bytes
- `fileModTime(path)` - Get file modification time
- `fileAge(path)` - Get duration since last modified

## Managing State

Persist data across executions and share information between workflow steps.
Expand Down
12 changes: 6 additions & 6 deletions docs/guide/executables.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,11 +327,11 @@ executables:
- `method`: HTTP method (GET, POST, PUT, PATCH, DELETE)
- `url`: Request URL (required)
- `headers`: Custom headers
- `body`: Request body
- `body`: Request body with Expr templating
- `timeout`: Request timeout
- `validStatusCodes`: Acceptable status codes
- `logResponse`: Log response body
- `transformResponse`: Transform response with Expr
- `transformResponse`: Transform response with Expr templating
- `responseFile`: Save response to file

### render - Dynamic Documentation
Expand All @@ -351,16 +351,16 @@ executables:
```markdown
# System Status

Current time: {{ .timestamp }}
Current time: {{ data["timestamp"] }}

## Services
{{- range .services }}
- **{{ .name }}**: {{ .status }}
- **{{ .name }}**: {{ data["status"] }}
{{- end }}

## Metrics
- CPU: {{ .cpu }}%
- Memory: {{ .memory }}%
- CPU: {{ data["cpu"] }}%
- Memory: {{ data["memory"] }}%
```

**Options:**
Expand Down
78 changes: 78 additions & 0 deletions docs/guide/secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,81 @@ flow vault create team --type age --recipients key1,key2,key3 --identity-file ~/
flow vault create team --type age --recipients key1,key2,key3 --identity-env MY_IDENTITY
```

#### **Unencrypted**
A simple vault that stores secrets in plain text JSON files.
This is not recommended for production use but can be useful for development or testing.

```shell
# Create an unencrypted vault
flow vault create dev --type unencrypted
```


#### **Keyring**

A vault that uses your operating system's keyring for managing secrets.
This is a good option for personal use where you want seamless integration with your OS security.

```shell
# Create a keyring vault
flow vault create dev --type keyring
```

#### **External (other CLI tools)**

An external vault that uses executes an external CLI tool via shell commands to manage secrets.
This allows you to integrate with existing secret management systems.

First you have to define the external vault configuration in JSON format. Here is a sample one that uses the `pass` CLI tool:

```json
{
"id": "pass",
"type": "external",
"external": {
"get": {
"cmd": "pass show {{key}}",
"output": "{{output}}"
},
"set": {
"cmd": "pass insert -e {{key}}",
"input": "{{value}}"
},
"delete": {
"cmd": "pass rm -f {{key}}"
},
"list": {
"cmd": "pass ls",
"output": "{{output}}"
},
"environment": {
"PASSWORD_STORE_DIR": "$PASSWORD_STORE_DIR"
},
"timeout": "30s"
}
}
```

> [!INFO]
> See the [flowexec/vault examples](https://github.com/flowexec/vault/tree/v0.2.1/examples) for sample configurations for popular CLI tools like Bitwarden, 1Password, AWS SSM, and more.


```shell
# Create an external vault
flow vault create passwords --type external --config /path/to/config.json
```

**Template Variables**

Available in `cmd` and `output` fields:

- `{{key}}` - The secret key/name
- `{{value}}` - The secret value (for set operations)
- `{{env["VariableName"]}}`- Environment variable value
- `{{output}}` - Raw command output (for output templates)

All [Expr language](https://expr-lang.org/docs/language-definition) operators and functions can be used in the command templates, allowing for powerful dynamic secret management.

<!-- tabs:end -->

#### Authentication
Expand All @@ -109,6 +184,9 @@ If you did not provide a key or file, these default environment variables will b

- For AES256 vaults: `FLOW_VAULT_KEY` environment variable
- For Age vaults: `FLOW_VAULT_IDENTITY` environment variable
- For Unencrypted vaults: no key is needed, it stores secrets in plain text
- For Keyring vaults: no key is needed, it uses the OS keyring directly
- For External vaults: no key is needed, it uses the external CLI tool directly. Auth may be required by the tool itself

At least one of the key or file will be used. You can configure key storage during vault creation:

Expand Down
2 changes: 1 addition & 1 deletion docs/schemas/template_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
],
"properties": {
"asTemplate": {
"description": "If true, the artifact will be copied as a template file. The file will be rendered using Go templating from \nthe form data. [Sprig functions](https://masterminds.github.io/sprig/) are available for use in the template.\n",
"description": "If true, the artifact will be copied as a template file. The file will be rendered using Go templating from \nthe form data. [Expr language functions](https://expr-lang.org/docs/language-definition) are available for use in the template.\n",
"type": "boolean",
"default": false
},
Expand Down
Loading