fix(mcp): CORS-aware scaffold, cors.wildcard rule, server-not-appended FP; v1.1.2
This commit is contained in:
@@ -6,6 +6,28 @@ This module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [1.1.2] — 2026-08-08
|
||||||
|
|
||||||
|
Patch. Fixes surfaced by an adversarial review plus the CORS wildcard trap.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`cors.wildcard-noop` rule** (`validate_snippet`). Flags `mw.CORS(...)` with a `"*"` literal —
|
||||||
|
which the framework now rejects at boot — and points to `CORSAllowAll()` or explicit origins.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **`get_scaffold`**: CORS is now env-gated (`CORSAllowAll` in local, `mw.CORS(origins)` elsewhere);
|
||||||
|
composes `launcher.Config` and `health.Config` so `EINHERJAR_COMPONENT_STOP_TIMEOUT` and
|
||||||
|
`EINHERJAR_HEALTH_CHECK_TIMEOUT` are reachable and appear in `.env.example`; the health hook uses
|
||||||
|
`health.NewHandlerWithConfig(...).ServeHTTP`; `.env.example` no longer emits `APP_CORS_ORIGINS=*`.
|
||||||
|
- **`wire` builtin**: documents the CORS convention and recommends `check_env`'s `composes` selectors.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **`web.server-not-appended`** no longer false-positives on a correctly-appended server; it fires
|
||||||
|
only when `server.New` is present and `.Append` is absent.
|
||||||
|
|
||||||
## [1.1.1] — 2026-08-07
|
## [1.1.1] — 2026-08-07
|
||||||
|
|
||||||
Patch. `check_env` gains struct-level granularity so it can catch dead vars that module
|
Patch. `check_env` gains struct-level granularity so it can catch dead vars that module
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# einherjar/mcp
|
# einherjar/mcp
|
||||||
|
|
||||||
[](https://code.nochebuena.dev/einherjar/mcp)
|
[](https://code.nochebuena.dev/einherjar/mcp)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://go.dev)
|
[](https://go.dev)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
serverName = "einherjar-mcp"
|
serverName = "einherjar-mcp"
|
||||||
serverVersion = "v1.1.1"
|
serverVersion = "v1.1.2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -145,17 +145,21 @@ and then failing at boot because nobody knew which variables to set.
|
|||||||
- **App-owned vars (`APP_*`)** — like `APP_JWT_SECRET` above: the framework can't know these, so
|
- **App-owned vars (`APP_*`)** — like `APP_JWT_SECRET` above: the framework can't know these, so
|
||||||
keeping them in `.env.example` is your discipline, not something it can name-check.
|
keeping them in `.env.example` is your discipline, not something it can name-check.
|
||||||
|
|
||||||
After you compose a component, run **`check_env`** with the modules the app composes: it flags
|
After you compose a component, run **`check_env`** with what the app composes: it flags
|
||||||
`EINHERJAR_*` names that don't exist, required vars you forgot to document, and vars set for a
|
`EINHERJAR_*` names that don't exist, required vars you forgot to document, and vars set for a
|
||||||
module you don't actually compose (dead vars). `get_scaffold` already emits a `.env.example`
|
config you don't actually compose (dead vars). Prefer the **`composes`** input (exact struct
|
||||||
derived from these same tags, so the starting point is correct by construction.
|
selectors like `web/server/Config`) over `modules` — it catches struct-level dead vars (e.g.
|
||||||
|
`EINHERJAR_SERVER_CORS_ORIGINS` lives on `web.Config`, not `server.Config`). `get_scaffold` already
|
||||||
|
emits a `.env.example` derived from these same tags, so the starting point is correct by construction.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# .env.example — copy to .env for local dev. Every var the app reads lives here.
|
# .env.example — copy to .env for local dev. Every var the app reads lives here.
|
||||||
|
|
||||||
# ── App ───────────────────────────────────────────────────────────────────
|
# ── App ───────────────────────────────────────────────────────────────────
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
APP_CORS_ORIGINS=*
|
# APP_CORS_ORIGINS — explicit origins for non-local envs (comma-separated).
|
||||||
|
# Local uses mw.CORSAllowAll() and ignores this; "*" is rejected by mw.CORS — never use it.
|
||||||
|
APP_CORS_ORIGINS=
|
||||||
APP_JWT_SECRET=change-me
|
APP_JWT_SECRET=change-me
|
||||||
APP_JWT_ISSUER=myapp
|
APP_JWT_ISSUER=myapp
|
||||||
|
|
||||||
@@ -225,11 +229,20 @@ func Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db := postgres.New(logger, cfg.PG)
|
db := postgres.New(logger, cfg.PG)
|
||||||
|
|
||||||
|
// CORS convention: allow-all in local dev, explicit origins everywhere else.
|
||||||
|
// mw.CORS panics on "*" (it matches no real origin) — allow-all is mw.CORSAllowAll,
|
||||||
|
// never a "*" in APP_CORS_ORIGINS.
|
||||||
|
corsMW := mw.CORSAllowAll()
|
||||||
|
if !strings.EqualFold(cfg.AppEnv, "local") {
|
||||||
|
corsMW = mw.CORS(cfg.CORSOrigins)
|
||||||
|
}
|
||||||
|
|
||||||
srv := server.New(logger, cfg.Server,
|
srv := server.New(logger, cfg.Server,
|
||||||
server.WithMiddleware(
|
server.WithMiddleware(
|
||||||
mw.RequestID(uuid.NewString),
|
mw.RequestID(uuid.NewString),
|
||||||
mw.Recover(logger),
|
mw.Recover(logger),
|
||||||
mw.CORS(cfg.CORSOrigins),
|
corsMW,
|
||||||
mw.RequestLogger(logger),
|
mw.RequestLogger(logger),
|
||||||
authjwt.AuthMiddleware(logger, signer, publicPaths),
|
authjwt.AuthMiddleware(logger, signer, publicPaths),
|
||||||
authmw.EnrichmentMiddleware(logger, &claimsEnricher{}),
|
authmw.EnrichmentMiddleware(logger, &claimsEnricher{}),
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package rules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The web/mw CORS convention: mw.CORS uses exact-origin matching and rejects "*"
|
||||||
|
// at construction (panic). Passing "*" is the trap this rule catches before runtime —
|
||||||
|
// allow-all is done with mw.CORSAllowAll(), gated by env in application code.
|
||||||
|
func init() {
|
||||||
|
registered = append(registered,
|
||||||
|
Rule{
|
||||||
|
ID: "cors.wildcard-noop",
|
||||||
|
Severity: SeverityError,
|
||||||
|
Module: "web",
|
||||||
|
Check: checkCORSWildcard,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkCORSWildcard flags any call to <pkg>.CORS(...) whose arguments contain a
|
||||||
|
// "*" string literal — which mw.CORS rejects (panics) at boot.
|
||||||
|
func checkCORSWildcard(c *Context) []Finding {
|
||||||
|
var hits []Finding
|
||||||
|
ast.Inspect(c.File, func(n ast.Node) bool {
|
||||||
|
call, ok := n.(*ast.CallExpr)
|
||||||
|
if !ok || !strings.HasSuffix(exprName(call.Fun), ".CORS") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, arg := range call.Args {
|
||||||
|
ast.Inspect(arg, func(m ast.Node) bool {
|
||||||
|
lit, ok := m.(*ast.BasicLit)
|
||||||
|
if ok && lit.Kind == token.STRING && strings.Trim(lit.Value, `"`) == "*" {
|
||||||
|
hits = append(hits, Finding{
|
||||||
|
Message: `mw.CORS with "*" panics at construction — "*" matches no real origin (exact-match only)`,
|
||||||
|
Hint: "Use mw.CORSAllowAll() for local development, or list explicit origins.",
|
||||||
|
Line: c.Fset.Position(call.Pos()).Line,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return hits
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package rules
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
const corsWildcardSnippet = `package wire
|
||||||
|
|
||||||
|
import "code.nochebuena.dev/einherjar/web/mw"
|
||||||
|
|
||||||
|
func f() { _ = mw.CORS([]string{"*"}) }
|
||||||
|
`
|
||||||
|
|
||||||
|
const serverAppendedSnippet = `package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.nochebuena.dev/einherjar/core/launcher"
|
||||||
|
"code.nochebuena.dev/einherjar/web/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func f() {
|
||||||
|
srv := server.New(logger, cfg)
|
||||||
|
lc := launcher.New(logger)
|
||||||
|
lc.Append(srv)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const serverNotAppendedSnippet = `package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.nochebuena.dev/einherjar/core/launcher"
|
||||||
|
"code.nochebuena.dev/einherjar/web/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func f() {
|
||||||
|
srv := server.New(logger, cfg)
|
||||||
|
lc := launcher.New(logger)
|
||||||
|
_ = srv
|
||||||
|
_ = lc
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestCORSWildcardFires(t *testing.T) {
|
||||||
|
got := findingsFor(Run(corsWildcardSnippet), "cors.wildcard-noop")
|
||||||
|
if len(got) == 0 {
|
||||||
|
t.Fatal("cors.wildcard-noop did not fire on mw.CORS([]string{\"*\"})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerAppendedNoFalsePositive(t *testing.T) {
|
||||||
|
if got := findingsFor(Run(serverAppendedSnippet), "web.server-not-appended"); len(got) != 0 {
|
||||||
|
t.Errorf("web.server-not-appended false-positived on an appended server: %+v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerNotAppendedFires(t *testing.T) {
|
||||||
|
if got := findingsFor(Run(serverNotAppendedSnippet), "web.server-not-appended"); len(got) == 0 {
|
||||||
|
t.Error("web.server-not-appended should fire when server.New is not appended")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -268,19 +268,15 @@ var registered = []Rule{
|
|||||||
if !c.Importing("einherjar/web/server") || !c.Importing("einherjar/core/launcher") {
|
if !c.Importing("einherjar/web/server") || !c.Importing("einherjar/core/launcher") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !c.Called(".Append") {
|
// Fire only when a server is constructed but never appended — an appended
|
||||||
|
// server (lc.Append(srv)) is correctly managed, so stay silent (no false positive).
|
||||||
|
if !c.Called("server.New") || c.Called(".Append") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Heuristic: warn if server.New is constructed but not appended via .Append.
|
|
||||||
// We can't statically prove the argument was the server, so this is informational.
|
|
||||||
if c.Called("server.New") {
|
|
||||||
return []Finding{{
|
return []Finding{{
|
||||||
Severity: SeverityInfo,
|
Message: "web/server constructed but never appended to the launcher — its lifecycle won't be managed",
|
||||||
Message: "web/server is constructed — ensure it is passed to launcher.Append() so its lifecycle is managed",
|
Hint: "lc.Append(srv) so the launcher starts and gracefully stops the HTTP server",
|
||||||
Hint: "lc.Append(srv) lets the launcher start and gracefully stop the HTTP server",
|
|
||||||
}}
|
}}
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,14 @@ func TestRenderEnvExample(t *testing.T) {
|
|||||||
// Required, no default → uncommented with a runnable dev value.
|
// Required, no default → uncommented with a runnable dev value.
|
||||||
mustContain := []string{
|
mustContain := []string{
|
||||||
"APP_ENV=local",
|
"APP_ENV=local",
|
||||||
"APP_CORS_ORIGINS=*",
|
"APP_CORS_ORIGINS=",
|
||||||
"EINHERJAR_PG_HOST=localhost",
|
"EINHERJAR_PG_HOST=localhost",
|
||||||
"EINHERJAR_PG_USER=postgres",
|
"EINHERJAR_PG_USER=postgres",
|
||||||
"EINHERJAR_PG_PASSWORD=postgres",
|
"EINHERJAR_PG_PASSWORD=postgres",
|
||||||
"EINHERJAR_PG_NAME=myapp",
|
"EINHERJAR_PG_NAME=myapp",
|
||||||
|
// launcher + health composed → their (defaulted) vars are documented, commented.
|
||||||
|
"# EINHERJAR_COMPONENT_STOP_TIMEOUT=15s",
|
||||||
|
"# EINHERJAR_HEALTH_CHECK_TIMEOUT=5s",
|
||||||
// logz is composed → its vars (both defaulted) are documented, commented.
|
// logz is composed → its vars (both defaulted) are documented, commented.
|
||||||
"# EINHERJAR_LOG_LEVEL=INFO",
|
"# EINHERJAR_LOG_LEVEL=INFO",
|
||||||
"# EINHERJAR_LOG_JSON=false",
|
"# EINHERJAR_LOG_JSON=false",
|
||||||
@@ -54,6 +57,7 @@ func TestRenderEnvExample(t *testing.T) {
|
|||||||
mustNotContain := []string{
|
mustNotContain := []string{
|
||||||
"EINHERJAR_PG_DATABASE",
|
"EINHERJAR_PG_DATABASE",
|
||||||
"EINHERJAR_SERVER_ADDR",
|
"EINHERJAR_SERVER_ADDR",
|
||||||
|
"APP_CORS_ORIGINS=*", // the wildcard trap must never be emitted
|
||||||
// CORS lives on web.Config, which the scaffold does not compose — so it
|
// CORS lives on web.Config, which the scaffold does not compose — so it
|
||||||
// must not be emitted (it would be a dead var).
|
// must not be emitted (it would be a dead var).
|
||||||
"EINHERJAR_SERVER_CORS_ORIGINS",
|
"EINHERJAR_SERVER_CORS_ORIGINS",
|
||||||
|
|||||||
+35
-17
@@ -100,6 +100,9 @@ func main() {
|
|||||||
const tplWire = `package wire
|
const tplWire = `package wire
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"code.nochebuena.dev/einherjar/core/launcher"
|
"code.nochebuena.dev/einherjar/core/launcher"
|
||||||
@@ -127,19 +130,29 @@ func Run() error {
|
|||||||
logger := logz.New(logCfg)
|
logger := logz.New(logCfg)
|
||||||
|
|
||||||
db := postgres.New(logger, cfg.PG)
|
db := postgres.New(logger, cfg.PG)
|
||||||
|
|
||||||
|
// CORS: allow-all in local dev; explicit origins elsewhere. mw.CORS panics on
|
||||||
|
// "*", so never pass a wildcard through APP_CORS_ORIGINS outside local.
|
||||||
|
var corsMW func(http.Handler) http.Handler
|
||||||
|
if strings.EqualFold(cfg.AppEnv, "local") {
|
||||||
|
corsMW = mw.CORSAllowAll()
|
||||||
|
} else {
|
||||||
|
corsMW = mw.CORS(cfg.CORSOrigins)
|
||||||
|
}
|
||||||
|
|
||||||
srv := server.New(logger, cfg.Server,
|
srv := server.New(logger, cfg.Server,
|
||||||
server.WithMiddleware(
|
server.WithMiddleware(
|
||||||
mw.RequestID(uuid.NewString),
|
mw.RequestID(uuid.NewString),
|
||||||
mw.Recover(logger),
|
mw.Recover(logger),
|
||||||
mw.CORS(cfg.CORSOrigins),
|
corsMW,
|
||||||
mw.RequestLogger(logger),
|
mw.RequestLogger(logger),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
lc := launcher.New(logger)
|
lc := launcher.New(logger, cfg.Launcher)
|
||||||
lc.Append(db, srv)
|
lc.Append(db, srv)
|
||||||
|
|
||||||
withHealth(lc, srv)
|
withHealth(lc, srv, logger, cfg.Health, db)
|
||||||
// … one withFeature(lc, srv, …) call per feature in your domain.
|
// … one withFeature(lc, srv, …) call per feature in your domain.
|
||||||
|
|
||||||
return lc.Run()
|
return lc.Run()
|
||||||
@@ -149,22 +162,19 @@ func Run() error {
|
|||||||
const tplHealth = `package wire
|
const tplHealth = `package wire
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"code.nochebuena.dev/einherjar/contracts/logging"
|
||||||
|
"code.nochebuena.dev/einherjar/contracts/observability"
|
||||||
"code.nochebuena.dev/einherjar/core/launcher"
|
"code.nochebuena.dev/einherjar/core/launcher"
|
||||||
|
"code.nochebuena.dev/einherjar/web/health"
|
||||||
"code.nochebuena.dev/einherjar/web/server"
|
"code.nochebuena.dev/einherjar/web/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
// withHealth registers a liveness endpoint — the simplest with<Feature> hook.
|
// withHealth wires a concurrent health endpoint from the app's Checkable components
|
||||||
// A real feature hook takes its deps (logging.Logger, postgres.Provider, …) and
|
// (db, cache, …). health.NewHandlerWithConfig honors EINHERJAR_HEALTH_CHECK_TIMEOUT
|
||||||
// wires repo→service→handler inside the closure; see get_example("wire").
|
// through cfg. Add more with<Feature> hooks the same way; see get_example("wire").
|
||||||
func withHealth(lc launcher.Launcher, srv server.Server) {
|
func withHealth(lc launcher.Launcher, srv server.Server, logger logging.Logger, cfg health.Config, checks ...observability.Checkable) {
|
||||||
lc.BeforeStart(func() error {
|
lc.BeforeStart(func() error {
|
||||||
srv.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
srv.Get("/health", health.NewHandlerWithConfig(logger, cfg, checks...).ServeHTTP)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
_, _ = w.Write([]byte(` + "`" + `{"status":"ok"}` + "`" + `))
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -181,22 +191,26 @@ package config
|
|||||||
import (
|
import (
|
||||||
"github.com/caarlos0/env/v11"
|
"github.com/caarlos0/env/v11"
|
||||||
|
|
||||||
|
"code.nochebuena.dev/einherjar/core/launcher"
|
||||||
"code.nochebuena.dev/einherjar/core/logz"
|
"code.nochebuena.dev/einherjar/core/logz"
|
||||||
"code.nochebuena.dev/einherjar/db-postgres"
|
"code.nochebuena.dev/einherjar/db-postgres"
|
||||||
|
"code.nochebuena.dev/einherjar/web/health"
|
||||||
"code.nochebuena.dev/einherjar/web/server"
|
"code.nochebuena.dev/einherjar/web/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is the fully-resolved startup configuration. caarlos0/env recurses into
|
// Config is the fully-resolved startup configuration. caarlos0/env recurses into
|
||||||
// the nested framework configs, populating their EINHERJAR_LOG_* / EINHERJAR_SERVER_* /
|
// the nested framework configs, populating their EINHERJAR_* tags from the
|
||||||
// EINHERJAR_PG_* tags from the environment next to the app-owned APP_* fields.
|
// environment next to the app-owned APP_* fields.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AppEnv string ` + "`" + `env:"APP_ENV" envDefault:"local"` + "`" + `
|
AppEnv string ` + "`" + `env:"APP_ENV" envDefault:"local"` + "`" + `
|
||||||
CORSOrigins []string ` + "`" + `env:"APP_CORS_ORIGINS" envSeparator:","` + "`" + `
|
CORSOrigins []string ` + "`" + `env:"APP_CORS_ORIGINS" envSeparator:","` + "`" + `
|
||||||
|
|
||||||
// Framework component configs — composed verbatim; their EINHERJAR_* tags
|
// Framework component configs — composed verbatim; their EINHERJAR_* tags
|
||||||
// load through this same env.Parse call.
|
// load through this same env.Parse call.
|
||||||
|
Launcher launcher.Config // EINHERJAR_COMPONENT_STOP_TIMEOUT
|
||||||
Log logz.Config // EINHERJAR_LOG_*
|
Log logz.Config // EINHERJAR_LOG_*
|
||||||
Server server.Config // EINHERJAR_SERVER_*
|
Server server.Config // EINHERJAR_SERVER_*
|
||||||
|
Health health.Config // EINHERJAR_HEALTH_CHECK_TIMEOUT
|
||||||
PG postgres.Config // EINHERJAR_PG_*
|
PG postgres.Config // EINHERJAR_PG_*
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,10 +237,14 @@ func renderEnvExample(idx *index.Index, app string) string {
|
|||||||
|
|
||||||
b.WriteString("# ── App (APP_*) ────────────────────────────────────────────────────────────\n")
|
b.WriteString("# ── App (APP_*) ────────────────────────────────────────────────────────────\n")
|
||||||
b.WriteString("APP_ENV=local\n")
|
b.WriteString("APP_ENV=local\n")
|
||||||
b.WriteString("APP_CORS_ORIGINS=*\n\n")
|
b.WriteString("# APP_CORS_ORIGINS — explicit origins for non-local envs (comma-separated).\n")
|
||||||
|
b.WriteString("# Local uses mw.CORSAllowAll() and ignores this; \"*\" is rejected by mw.CORS — never use it.\n")
|
||||||
|
b.WriteString("APP_CORS_ORIGINS=\n\n")
|
||||||
|
|
||||||
|
writeEnvSection(&b, "Einherjar: launcher", envspec.FindStruct(idx, "core", "launcher", "Config"), app)
|
||||||
writeEnvSection(&b, "Einherjar: logging", envspec.FindStruct(idx, "core", "logz", "Config"), app)
|
writeEnvSection(&b, "Einherjar: logging", envspec.FindStruct(idx, "core", "logz", "Config"), app)
|
||||||
writeEnvSection(&b, "Einherjar: HTTP server", envspec.FindStruct(idx, "web", "server", "Config"), app)
|
writeEnvSection(&b, "Einherjar: HTTP server", envspec.FindStruct(idx, "web", "server", "Config"), app)
|
||||||
|
writeEnvSection(&b, "Einherjar: health", envspec.FindStruct(idx, "web", "health", "Config"), app)
|
||||||
writeEnvSection(&b, "Einherjar: PostgreSQL", envspec.FindStruct(idx, "db-postgres", "", "Config"), app)
|
writeEnvSection(&b, "Einherjar: PostgreSQL", envspec.FindStruct(idx, "db-postgres", "", "Config"), app)
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user