Skip to content

fix: inject channel env vars into K8s manifests (closes #50)#54

Open
initializ-mk wants to merge 1 commit into
mainfrom
bug/channel-env-k8s-manifests
Open

fix: inject channel env vars into K8s manifests (closes #50)#54
initializ-mk wants to merge 1 commit into
mainfrom
bug/channel-env-k8s-manifests

Conversation

@initializ-mk
Copy link
Copy Markdown
Contributor

Summary

Fixes #50forge build and forge package now include communication-channel env vars (e.g. SLACK_BOT_TOKEN, TELEGRAM_BOT_TOKEN) in the generated Kubernetes deployment.yaml and secrets.yaml. They were silently dropped before; only the hardcoded path in cmd/package.go (docker-compose) injected them.

Approach

Both output paths now use a single canonical source: each project's per-channel YAML (<workDir>/<channel>-config.yaml). The _env suffix convention (bot_token_env: SLACK_BOT_TOKEN) is already the runtime contract via channels.ResolveEnvVars — this PR just makes the build pipeline honor the same convention.

Layer Change
forge-cli/channels/env.go (new) EnvVarsFromConfig(workDir, channels) returns the sorted/deduped union of env-var names across channel YAMLs, plus a list of channels whose config file is missing.
forge-cli/build/channels_stage.go (new) ChannelsStage unions the helper's output into Spec.Requirements.EnvRequired (creating Requirements if RequirementsStage left it nil for channels-only projects). Inserted in the pipeline between RequirementsStage and PolicyStage. Missing channel YAMLs surface a build warning, not an error.
forge-cli/cmd/package.go generateDockerCompose now accepts workDir and calls the same helper instead of a hardcoded case "slack" / case "telegram" switch.
forge-cli/cmd/build.go Inserted &build.ChannelsStage{} into the build pipeline.

Templates (deployment.yaml.tmpl, secrets.yaml.tmpl) are untouched — they already iterate RequiredEnvVars, so unioning channel env vars into that field is enough.

Behavior change worth calling out

forge package --with-channels no longer injects SLACK_SIGNING_SECRET into docker-compose.yaml. The prior hardcoded switch listed it, but the Slack adapter is Socket Mode (forge-plugins/channels/slack/slack.go:55-67) and never reads it. The shipped slack-config.yaml.tmpl also doesn't declare it. Switching to the YAML-driven source corrects that long-standing inaccuracy.

Operators who need SLACK_SIGNING_SECRET for a custom adapter setup can add signing_secret_env: SLACK_SIGNING_SECRET to their slack-config.yaml — the helper will pick it up automatically. This flips the question from "is this in our hardcoded list?" to "is this in your project config?".

Tests

gofmt -l forge-core/ forge-cli/ forge-plugins/                        # clean
golangci-lint run ./{forge-core,forge-cli,forge-plugins}/...          # 0 issues
cd forge-core && go test ./...                                        # pass
cd forge-cli && go test ./...                                         # pass
cd forge-plugins && go test ./...                                     # pass

New tests:

  • forge-cli/channels/env_test.go — extracts _env suffix settings, ignores non-env settings (mode, webhook_path), dedups across channels, reports missing config files, returns clean empty results for no channels, surfaces parse errors.
  • forge-cli/build/channels_stage_test.go:
    • TestChannelsStage_UnionsWithSkillEnvRequired — channel env vars merge with skill EnvRequired.
    • TestChannelsStage_PopulatesRequirementsWhenNil — channels-only projects.
    • TestChannelsStage_NoChannels — no-op when cfg.Channels is empty.
    • TestChannelsStage_FlowsThroughToK8sManifests — end-to-end regression for Communication channel env vars are missing from generated Kubernetes manifests #50: with slack-config.yaml and telegram-config.yaml on disk, running ChannelsStage then K8sStage produces a deployment.yaml and secrets.yaml containing SLACK_APP_TOKEN, SLACK_BOT_TOKEN, TELEGRAM_BOT_TOKEN referenced via secretKeyRef.
    • TestChannelsStage_MissingConfigWarns — missing channel YAML produces a warning, build continues.

Updated:

  • forge-cli/cmd/package_test.goTestGenerateDockerCompose now seeds channel YAMLs in the temp workDir, passes the new workDir parameter, asserts the YAML-declared vars are present, and asserts SLACK_SIGNING_SECRET is absent.

Acceptance criteria from #50

  • With channels: [slack, telegram] in forge.yaml, k8s/secrets.yaml lists the declared channel env vars and k8s/deployment.yaml references them via secretKeyRef.
  • Adding a new channel adapter requires zero edits to k8s_stage.go, requirements_stage.go, template_data.go, any template, or cmd/package.go. The new adapter ships its own <name>-config.yaml template through forge init and the helper picks it up.
  • docker-compose.yaml (--with-channels) and k8s/{deployment,secrets}.yaml produce a consistent set of channel env vars from the same source.
  • Skill env vars continue to appear in the K8s manifests (no regression — union logic preserves them).
  • Missing channel config files do not fail the build; they surface as build warnings so the operator sees what's missing.

The AC also said "if a channel adapter declares an env var as optional vs. required, the K8s manifest reflects that distinction". The current _env convention in the channel YAMLs makes no required/optional distinction — every _env setting is treated as required (matching the Slack/Telegram adapters, which all fail at Init time when their tokens are missing). Adding an optional flavor (e.g. _env_optional suffix) is a clean follow-up that builds on this PR's helper.

Test plan

  • forge init my-agent --framework forge, add channels: [slack, telegram] to forge.yaml, run forge build. Confirm k8s/secrets.yaml lists SLACK_APP_TOKEN, SLACK_BOT_TOKEN, TELEGRAM_BOT_TOKEN and k8s/deployment.yaml references them via secretKeyRef.
  • Run forge package --with-channels. Confirm docker-compose.yaml contains the same channel env-var set.
  • Configure channels: [slack] but delete slack-config.yaml. Confirm forge build succeeds with a warning that mentions slack.
  • Add custom_env: MY_CUSTOM_SECRET to a project's slack-config.yaml. Re-run forge build. Confirm MY_CUSTOM_SECRET flows into both the K8s manifest and docker-compose.yaml with no code changes.

The K8s manifests generated by `forge build` / `forge package`
included only skill-aggregated env vars. Channel env vars (e.g.
SLACK_BOT_TOKEN, TELEGRAM_BOT_TOKEN) were silently dropped, even
though docker-compose got them via a hardcoded switch in package.go.

Unify both paths on a single canonical source: each project's
per-channel YAML (e.g. slack-config.yaml), which already uses the
`_env` suffix convention that runtime channels.ResolveEnvVars honors.

- forge-cli/channels/env.go: EnvVarsFromConfig(workDir, channels)
  reads each <channel>-config.yaml, extracts every `_env`-suffixed
  setting value, returns sorted/deduped env-var names plus a list of
  channels whose config file is missing.
- forge-cli/build/channels_stage.go: ChannelsStage unions the helper's
  output into Spec.Requirements.EnvRequired so the existing K8s
  templates pick them up. Creates Requirements when channels are
  configured but no skills are. Inserted into the build pipeline
  between RequirementsStage and PolicyStage.
- forge-cli/cmd/package.go: generateDockerCompose now accepts workDir
  and uses the helper instead of a hardcoded slack/telegram switch.

Behavior change: SLACK_SIGNING_SECRET is no longer injected into
docker-compose. The prior hardcoded switch listed it but the Slack
adapter is Socket Mode and never reads it. Operators who need it can
add `signing_secret_env: SLACK_SIGNING_SECRET` to slack-config.yaml.

Tests:
- channels/env_test.go: extraction, dedup, missing file, parse error.
- build/channels_stage_test.go: union with skill envs, creation when
  Requirements is nil, no-op for projects without channels, missing
  config warns, end-to-end regression that runs ChannelsStage +
  K8sStage and asserts channel env vars appear via secretKeyRef in
  deployment.yaml and secrets.yaml.
- cmd/package_test.go: existing test updated to seed channel YAMLs
  and assert the new YAML-driven env-var set (SIGNING_SECRET no
  longer expected).

Adding a new channel adapter now requires zero edits to k8s_stage.go,
requirements_stage.go, template_data.go, any template, or package.go —
the adapter ships its own <name>-config.yaml template and the helper
picks it up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Communication channel env vars are missing from generated Kubernetes manifests

1 participant