Skip to content

Commit 2fd2f98

Browse files
authored
feat: add support for env files at ws and exec level (#292)
1 parent 303df49 commit 2fd2f98

File tree

22 files changed

+511
-10
lines changed

22 files changed

+511
-10
lines changed

cmd/internal/exec.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"os"
7+
"path/filepath"
78
"strings"
89
"time"
910

@@ -26,9 +27,11 @@ import (
2627
"github.com/flowexec/flow/internal/runner/request"
2728
"github.com/flowexec/flow/internal/runner/serial"
2829
"github.com/flowexec/flow/internal/services/store"
30+
"github.com/flowexec/flow/internal/utils/env"
2931
"github.com/flowexec/flow/internal/vault"
3032
vaultV2 "github.com/flowexec/flow/internal/vault/v2"
3133
"github.com/flowexec/flow/types/executable"
34+
"github.com/flowexec/flow/types/workspace"
3235
)
3336

3437
func RegisterExecCmd(ctx *context.Context, rootCmd *cobra.Command) {
@@ -125,7 +128,7 @@ func execFunc(ctx *context.Context, cmd *cobra.Command, verb executable.Verb, ar
125128

126129
if !e.IsExecutableFromWorkspace(ctx.CurrentWorkspace.AssignedName()) {
127130
logger.Log().FatalErr(fmt.Errorf(
128-
"e '%s' cannot be executed from workspace %s",
131+
"executable '%s' cannot be executed from workspace %s",
129132
ref,
130133
ctx.Config.CurrentWorkspace,
131134
))
@@ -140,8 +143,14 @@ func execFunc(ctx *context.Context, cmd *cobra.Command, verb executable.Verb, ar
140143
}
141144
_ = s.Close()
142145

143-
// add --param overrides to the env map
144146
envMap := make(map[string]string)
147+
// add workspace env variables to the env map
148+
if wsCfg, ok := ctx.WorkspacesCache.GetData().Workspaces[e.Workspace()]; !ok {
149+
logger.Log().Warnf("workspace %s not found in cache, skipping env file resolution", e.Workspace())
150+
} else {
151+
applyWorkspaceParameterOverrides(wsCfg, envMap)
152+
}
153+
// add --param overrides to the env map
145154
paramOverrides := flags.ValueFor[[]string](cmd, *flags.ParameterValueFlag, false)
146155
applyParameterOverrides(paramOverrides, envMap)
147156

@@ -395,6 +404,31 @@ func pendingFormFields(
395404
return pending
396405
}
397406

407+
//nolint:nestif
408+
func applyWorkspaceParameterOverrides(ws *workspace.Workspace, envMap map[string]string) {
409+
if len(ws.EnvFiles) > 0 {
410+
loaded, err := env.LoadEnvFromFiles(ws.EnvFiles, ws.Location())
411+
if err != nil {
412+
logger.Log().Errorf("failed loading env files for workspace %s: %v", ws.AssignedName(), err)
413+
}
414+
for k, v := range loaded {
415+
envMap[k] = v
416+
}
417+
} else {
418+
rootEnvFile := filepath.Join(ws.Location(), ".env")
419+
if _, err := os.Stat(rootEnvFile); err == nil {
420+
loaded, err := env.LoadEnvFromFiles([]string{rootEnvFile}, ws.Location())
421+
if err != nil {
422+
logger.Log().Errorf("failed loading root env file %s: %v", rootEnvFile, err)
423+
} else {
424+
for k, v := range loaded {
425+
envMap[k] = v
426+
}
427+
}
428+
}
429+
}
430+
}
431+
398432
func applyParameterOverrides(overrides []string, envMap map[string]string) {
399433
for _, override := range overrides {
400434
parts := strings.SplitN(override, "=", 2)

desktop/src-tauri/src/types/generated/flowfile.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -944,8 +944,13 @@ impl ::std::convert::From<::std::vec::Vec<ExecutableParallelRefConfig>>
944944
#[doc = " \"description\": \"A parameter is a value that can be passed to an executable and all of its sub-executables.\\nOnly one of `text`, `secretRef`, `prompt`, or `file` must be set. Specifying more than one will result in an error.\\n\","]
945945
#[doc = " \"type\": \"object\","]
946946
#[doc = " \"properties\": {"]
947+
#[doc = " \"envFile\": {"]
948+
#[doc = " \"description\": \"A path to a file containing environment variables to be passed to the executable.\\nThe file should contain one variable per line in the format `KEY=VALUE`.\\n\","]
949+
#[doc = " \"default\": \"\","]
950+
#[doc = " \"type\": \"string\""]
951+
#[doc = " },"]
947952
#[doc = " \"envKey\": {"]
948-
#[doc = " \"description\": \"The name of the environment variable that will be assigned the value.\","]
953+
#[doc = " \"description\": \"The name of the environment variable that will be assigned the value.\\n\\nWhen specified with `envFile`, only the environment variable with this name will be set.\\n\","]
949954
#[doc = " \"default\": \"\","]
950955
#[doc = " \"type\": \"string\""]
951956
#[doc = " },"]
@@ -975,7 +980,10 @@ impl ::std::convert::From<::std::vec::Vec<ExecutableParallelRefConfig>>
975980
#[doc = r" </details>"]
976981
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
977982
pub struct ExecutableParameter {
978-
#[doc = "The name of the environment variable that will be assigned the value."]
983+
#[doc = "A path to a file containing environment variables to be passed to the executable.\nThe file should contain one variable per line in the format `KEY=VALUE`.\n"]
984+
#[serde(rename = "envFile", default)]
985+
pub env_file: ::std::string::String,
986+
#[doc = "The name of the environment variable that will be assigned the value.\n\nWhen specified with `envFile`, only the environment variable with this name will be set.\n"]
979987
#[serde(rename = "envKey", default)]
980988
pub env_key: ::std::string::String,
981989
#[doc = "A path where the parameter value will be temporarily written to disk.\nThe file will be created before execution and cleaned up afterwards.\n"]
@@ -999,6 +1007,7 @@ impl ::std::convert::From<&ExecutableParameter> for ExecutableParameter {
9991007
impl ::std::default::Default for ExecutableParameter {
10001008
fn default() -> Self {
10011009
Self {
1010+
env_file: Default::default(),
10021011
env_key: Default::default(),
10031012
output_file: Default::default(),
10041013
prompt: Default::default(),
@@ -3468,6 +3477,7 @@ pub mod builder {
34683477
}
34693478
#[derive(Clone, Debug)]
34703479
pub struct ExecutableParameter {
3480+
env_file: ::std::result::Result<::std::string::String, ::std::string::String>,
34713481
env_key: ::std::result::Result<::std::string::String, ::std::string::String>,
34723482
output_file: ::std::result::Result<::std::string::String, ::std::string::String>,
34733483
prompt: ::std::result::Result<::std::string::String, ::std::string::String>,
@@ -3477,6 +3487,7 @@ pub mod builder {
34773487
impl ::std::default::Default for ExecutableParameter {
34783488
fn default() -> Self {
34793489
Self {
3490+
env_file: Ok(Default::default()),
34803491
env_key: Ok(Default::default()),
34813492
output_file: Ok(Default::default()),
34823493
prompt: Ok(Default::default()),
@@ -3486,6 +3497,16 @@ pub mod builder {
34863497
}
34873498
}
34883499
impl ExecutableParameter {
3500+
pub fn env_file<T>(mut self, value: T) -> Self
3501+
where
3502+
T: ::std::convert::TryInto<::std::string::String>,
3503+
T::Error: ::std::fmt::Display,
3504+
{
3505+
self.env_file = value
3506+
.try_into()
3507+
.map_err(|e| format!("error converting supplied value for env_file: {}", e));
3508+
self
3509+
}
34893510
pub fn env_key<T>(mut self, value: T) -> Self
34903511
where
34913512
T: ::std::convert::TryInto<::std::string::String>,
@@ -3543,6 +3564,7 @@ pub mod builder {
35433564
value: ExecutableParameter,
35443565
) -> ::std::result::Result<Self, super::error::ConversionError> {
35453566
Ok(Self {
3567+
env_file: value.env_file?,
35463568
env_key: value.env_key?,
35473569
output_file: value.output_file?,
35483570
prompt: value.prompt?,
@@ -3554,6 +3576,7 @@ pub mod builder {
35543576
impl ::std::convert::From<super::ExecutableParameter> for ExecutableParameter {
35553577
fn from(value: super::ExecutableParameter) -> Self {
35563578
Self {
3579+
env_file: Ok(value.env_file),
35573580
env_key: Ok(value.env_key),
35583581
output_file: Ok(value.output_file),
35593582
prompt: Ok(value.prompt),

desktop/src-tauri/src/types/generated/workspace.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,14 @@ impl
206206
#[doc = " \"default\": \"\","]
207207
#[doc = " \"type\": \"string\""]
208208
#[doc = " },"]
209+
#[doc = " \"envFiles\": {"]
210+
#[doc = " \"description\": \"A list of environment variable files to load for the workspace. These files should contain key-value pairs of environment variables.\\nBy default, the `.env` file in the workspace root is loaded if it exists.\\n\","]
211+
#[doc = " \"default\": [],"]
212+
#[doc = " \"type\": \"array\","]
213+
#[doc = " \"items\": {"]
214+
#[doc = " \"type\": \"string\""]
215+
#[doc = " }"]
216+
#[doc = " },"]
209217
#[doc = " \"executables\": {"]
210218
#[doc = " \"$ref\": \"#/definitions/ExecutableFilter\""]
211219
#[doc = " },"]
@@ -231,6 +239,13 @@ pub struct Workspace {
231239
#[doc = "The display name of the workspace. This is used in the interactive UI."]
232240
#[serde(rename = "displayName", default)]
233241
pub display_name: ::std::string::String,
242+
#[doc = "A list of environment variable files to load for the workspace. These files should contain key-value pairs of environment variables.\nBy default, the `.env` file in the workspace root is loaded if it exists.\n"]
243+
#[serde(
244+
rename = "envFiles",
245+
default,
246+
skip_serializing_if = "::std::vec::Vec::is_empty"
247+
)]
248+
pub env_files: ::std::vec::Vec<::std::string::String>,
234249
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
235250
pub executables: ::std::option::Option<ExecutableFilter>,
236251
#[serde(default = "defaults::workspace_tags")]
@@ -253,6 +268,7 @@ impl ::std::default::Default for Workspace {
253268
description: Default::default(),
254269
description_file: Default::default(),
255270
display_name: Default::default(),
271+
env_files: Default::default(),
256272
executables: Default::default(),
257273
tags: defaults::workspace_tags(),
258274
verb_aliases: Default::default(),
@@ -327,6 +343,8 @@ pub mod builder {
327343
description: ::std::result::Result<::std::string::String, ::std::string::String>,
328344
description_file: ::std::result::Result<::std::string::String, ::std::string::String>,
329345
display_name: ::std::result::Result<::std::string::String, ::std::string::String>,
346+
env_files:
347+
::std::result::Result<::std::vec::Vec<::std::string::String>, ::std::string::String>,
330348
executables: ::std::result::Result<
331349
::std::option::Option<super::ExecutableFilter>,
332350
::std::string::String,
@@ -341,6 +359,7 @@ pub mod builder {
341359
description: Ok(Default::default()),
342360
description_file: Ok(Default::default()),
343361
display_name: Ok(Default::default()),
362+
env_files: Ok(Default::default()),
344363
executables: Ok(Default::default()),
345364
tags: Ok(super::defaults::workspace_tags()),
346365
verb_aliases: Ok(Default::default()),
@@ -381,6 +400,16 @@ pub mod builder {
381400
.map_err(|e| format!("error converting supplied value for display_name: {}", e));
382401
self
383402
}
403+
pub fn env_files<T>(mut self, value: T) -> Self
404+
where
405+
T: ::std::convert::TryInto<::std::vec::Vec<::std::string::String>>,
406+
T::Error: ::std::fmt::Display,
407+
{
408+
self.env_files = value
409+
.try_into()
410+
.map_err(|e| format!("error converting supplied value for env_files: {}", e));
411+
self
412+
}
384413
pub fn executables<T>(mut self, value: T) -> Self
385414
where
386415
T: ::std::convert::TryInto<::std::option::Option<super::ExecutableFilter>>,
@@ -421,6 +450,7 @@ pub mod builder {
421450
description: value.description?,
422451
description_file: value.description_file?,
423452
display_name: value.display_name?,
453+
env_files: value.env_files?,
424454
executables: value.executables?,
425455
tags: value.tags?,
426456
verb_aliases: value.verb_aliases?,
@@ -433,6 +463,7 @@ pub mod builder {
433463
description: Ok(value.description),
434464
description_file: Ok(value.description_file),
435465
display_name: Ok(value.display_name),
466+
env_files: Ok(value.env_files),
436467
executables: Ok(value.executables),
437468
tags: Ok(value.tags),
438469
verb_aliases: Ok(value.verb_aliases),

desktop/src/types/generated/flowfile.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,17 @@ export interface ExecutableArgument {
365365
*
366366
*/
367367
export interface ExecutableParameter {
368+
/**
369+
* A path to a file containing environment variables to be passed to the executable.
370+
* The file should contain one variable per line in the format `KEY=VALUE`.
371+
*
372+
*/
373+
envFile?: string;
368374
/**
369375
* The name of the environment variable that will be assigned the value.
376+
*
377+
* When specified with `envFile`, only the environment variable with this name will be set.
378+
*
370379
*/
371380
envKey?: string;
372381
/**

desktop/src/types/generated/workspace.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ export interface Workspace {
2929
* The display name of the workspace. This is used in the interactive UI.
3030
*/
3131
displayName?: string;
32+
/**
33+
* A list of environment variable files to load for the workspace. These files should contain key-value pairs of environment variables.
34+
* By default, the `.env` file in the workspace root is loaded if it exists.
35+
*
36+
*/
37+
envFiles?: string[];
3238
executables?: ExecutableFilter;
3339
tags?: CommonTags;
3440
verbAliases?: VerbAliases;

docs/guide/executables.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ executables:
6161
6262
Customize executable behavior with environment variables or temporary files using `params` or `args`.
6363

64+
> [!INFO]
65+
> Executables inherit environment variables from their parent executable, workspace, and system.
66+
>
67+
> By default, values defined in the `.env` file at the workspace root are automatically loaded. This can be overriden
68+
> in the workspace configuration file with the `envFiles` field.
69+
6470
### Parameters (`params`) <!-- {docsify-ignore} -->
6571

6672
Set environment data from various sources:
@@ -85,6 +91,11 @@ executables:
8591
# Static values
8692
- text: "production"
8793
envKey: DEPLOY_ENV
94+
95+
# Env File (key=value format)
96+
- envFile: "development.env"
97+
- envFile: "staging.env"
98+
envKey: SHARED_KEYS # Only load specific keys
8899
89100
# Saved to a file
90101
- secretRef: tls-cert
@@ -95,6 +106,7 @@ executables:
95106
- `secretRef`: Reference to vault secret
96107
- `prompt`: Interactive user input
97108
- `text`: Static value
109+
- `envFile`: Load environment variables from a file
98110

99111
### Arguments (`args`) <!-- {docsify-ignore} -->
100112

docs/guide/workspaces.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ verbAliases:
7979
run: ["start", "exec"]
8080
build: ["compile", "make"]
8181
# Set to {} to disable all aliases
82+
83+
# Environment variables to load for all executables in this workspace
84+
envFiles:
85+
- .env
86+
- .env.local
8287

8388
# Control executable discovery
8489
executables:
@@ -100,6 +105,7 @@ executables:
100105

101106
**Behavior Customization:**
102107
- `verbAliases`: Customize which verb synonyms are available
108+
- `envFiles`: List of environment files to load for all executables (the root `.env` is loaded by default)
103109

104110
> **Complete reference**: See the [workspace configuration schema](../types/workspace.md) for all available options.
105111

docs/schemas/flowfile_schema.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,13 @@
283283
"description": "A parameter is a value that can be passed to an executable and all of its sub-executables.\nOnly one of `text`, `secretRef`, `prompt`, or `file` must be set. Specifying more than one will result in an error.\n",
284284
"type": "object",
285285
"properties": {
286+
"envFile": {
287+
"description": "A path to a file containing environment variables to be passed to the executable.\nThe file should contain one variable per line in the format `KEY=VALUE`.\n",
288+
"type": "string",
289+
"default": ""
290+
},
286291
"envKey": {
287-
"description": "The name of the environment variable that will be assigned the value.",
292+
"description": "The name of the environment variable that will be assigned the value.\n\nWhen specified with `envFile`, only the environment variable with this name will be set.\n",
288293
"type": "string",
289294
"default": ""
290295
},

docs/schemas/workspace_schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@
6060
"type": "string",
6161
"default": ""
6262
},
63+
"envFiles": {
64+
"description": "A list of environment variable files to load for the workspace. These files should contain key-value pairs of environment variables.\nBy default, the `.env` file in the workspace root is loaded if it exists.\n",
65+
"type": "array",
66+
"default": [],
67+
"items": {
68+
"type": "string"
69+
}
70+
},
6371
"executables": {
6472
"$ref": "#/definitions/ExecutableFilter"
6573
},

docs/types/flowfile.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@ Only one of `text`, `secretRef`, `prompt`, or `file` must be set. Specifying mor
237237

238238
| Field | Description | Type | Default | Required |
239239
| ----- | ----------- | ---- | ------- | :--------: |
240-
| `envKey` | The name of the environment variable that will be assigned the value. | `string` | | |
240+
| `envFile` | A path to a file containing environment variables to be passed to the executable. The file should contain one variable per line in the format `KEY=VALUE`. | `string` | | |
241+
| `envKey` | The name of the environment variable that will be assigned the value. When specified with `envFile`, only the environment variable with this name will be set. | `string` | | |
241242
| `outputFile` | A path where the parameter value will be temporarily written to disk. The file will be created before execution and cleaned up afterwards. | `string` | | |
242243
| `prompt` | A prompt to be displayed to the user when collecting an input value. | `string` | | |
243244
| `secretRef` | A reference to a secret to be passed to the executable. | `string` | | |

0 commit comments

Comments
 (0)