Skip to content

Commit 4fa8033

Browse files
michaelmcneesclaude
andcommitted
feat(config): add env map for workflow expression variables (#74)
Add Env map[string]string field to Config struct, parsed from the env: section in mantle.yaml. Keys are normalized to uppercase to match the MANTLE_ENV_* convention used by the CEL evaluator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 965916c commit 4fa8033

2 files changed

Lines changed: 52 additions & 0 deletions

File tree

packages/engine/internal/config/config.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"log"
99
"net/url"
1010
"os"
11+
"strings"
1112
"time"
1213

1314
"github.com/dvflw/mantle/internal/budget"
@@ -30,6 +31,7 @@ type Config struct {
3031
GCP GCPConfig `mapstructure:"gcp"`
3132
Azure AzureConfig `mapstructure:"azure"`
3233
Tmp TmpConfig `mapstructure:"tmp"`
34+
Env map[string]string `mapstructure:"env"`
3335
}
3436

3537
// RetentionConfig holds data retention settings.
@@ -274,6 +276,16 @@ func Load(cmd *cobra.Command) (*Config, error) {
274276
return nil, err
275277
}
276278

279+
// Viper lowercases all map keys. Normalize env map keys to uppercase
280+
// so they match MANTLE_ENV_* convention and CEL expressions like env.APP_NAME.
281+
if len(cfg.Env) > 0 {
282+
normalized := make(map[string]string, len(cfg.Env))
283+
for k, v := range cfg.Env {
284+
normalized[strings.ToUpper(k)] = v
285+
}
286+
cfg.Env = normalized
287+
}
288+
277289
// Validate budget reset_day range.
278290
if cfg.Engine.Budget.ResetDay < 1 || cfg.Engine.Budget.ResetDay > 28 {
279291
if cfg.Engine.Budget.ResetMode == budget.ResetModeRolling {

packages/engine/internal/config/config_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,3 +309,43 @@ tmp:
309309
// path from file should still be present since env var didn't override it
310310
assert.Equal(t, "/tmp/file-path", cfg.Tmp.Path)
311311
}
312+
313+
func TestLoad_EnvMap(t *testing.T) {
314+
dir := t.TempDir()
315+
configFile := filepath.Join(dir, "mantle.yaml")
316+
err := os.WriteFile(configFile, []byte(`
317+
env:
318+
APP_NAME: "my-app"
319+
REGION: "us-east-1"
320+
DEBUG: "true"
321+
`), 0644)
322+
require.NoError(t, err)
323+
324+
cmd := newTestCommand()
325+
_ = cmd.Flags().Set("config", configFile)
326+
327+
cfg, err := Load(cmd)
328+
require.NoError(t, err)
329+
330+
assert.Equal(t, "my-app", cfg.Env["APP_NAME"])
331+
assert.Equal(t, "us-east-1", cfg.Env["REGION"])
332+
assert.Equal(t, "true", cfg.Env["DEBUG"])
333+
}
334+
335+
func TestLoad_EnvMapEmpty(t *testing.T) {
336+
dir := t.TempDir()
337+
configFile := filepath.Join(dir, "mantle.yaml")
338+
err := os.WriteFile(configFile, []byte(`
339+
log:
340+
level: "debug"
341+
`), 0644)
342+
require.NoError(t, err)
343+
344+
cmd := newTestCommand()
345+
_ = cmd.Flags().Set("config", configFile)
346+
347+
cfg, err := Load(cmd)
348+
require.NoError(t, err)
349+
350+
assert.Empty(t, cfg.Env)
351+
}

0 commit comments

Comments
 (0)