Omit empty fields from bootstrap config output

probod-bootstrap was writing empty strings and stub blocks such as
`esign: {}` into generated YAML. The post-marshal prune pass caused
part of that by stripping empty leaf strings while leaving empty
parent maps behind.

Drop the prune round-trip in WriteConfig and rely on struct-level
omitzero/omitempty tags plus custom IsZero() helpers on probodconfig.
Only include LLM providers when an API key is set, use a nil map for
extra API headers, and extend the dev-config Makefile recipe with the
local dev defaults already documented in .env.example.

Config loading is unchanged: omitted keys still decode to Go zero
values.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-07-01 10:15:28 +02:00
parent 2be3e18437
commit e22aaa8b67
17 changed files with 380 additions and 195 deletions

View File

@@ -19,8 +19,8 @@
# PROBOD_BASE_URL=http://localhost:8080 # PROBOD_BASE_URL=http://localhost:8080
# PROBOD_API_ADDR=:8080 # PROBOD_API_ADDR=:8080
# PROBOD_API_CORS_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5173,http://localhost:5174 # PROBOD_API_CORS_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5173,http://localhost:5174
# PROBOD_TRUST_CENTER_HTTP_ADDR=:80 # PROBOD_TRUST_CENTER_HTTP_ADDR=:10080
# PROBOD_TRUST_CENTER_HTTPS_ADDR=:443 # PROBOD_TRUST_CENTER_HTTPS_ADDR=:10443
# ── Observability ───────────────────────────────────────────────────── # ── Observability ─────────────────────────────────────────────────────
# PROBOD_METRICS_ADDR=localhost:8081 # PROBOD_METRICS_ADDR=localhost:8081

View File

@@ -201,18 +201,37 @@ $(CFG_DEV_OAUTH2_KEY):
cfg/dev.yaml: bin/probod-bootstrap $(CFG_DEV_OAUTH2_KEY) compose/pebble/certs/rootCA.pem $(wildcard $(DEV_ENV)) cfg/dev.yaml: bin/probod-bootstrap $(CFG_DEV_OAUTH2_KEY) compose/pebble/certs/rootCA.pem $(wildcard $(DEV_ENV))
@$(MKDIR) $(@D) @$(MKDIR) $(@D)
set -a; \ set -a; \
PROBOD_BASE_URL=http://localhost:8080; \
PROBOD_API_ADDR=:8080; \
PROBOD_ENCRYPTION_KEY="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; \ PROBOD_ENCRYPTION_KEY="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; \
PROBOD_AUTH_COOKIE_NAME=SSID; \
PROBOD_AUTH_COOKIE_DOMAIN=localhost; \
PROBOD_AUTH_COOKIE_SECRET="this-is-a-secure-secret-for-cookie-signing-at-least-32-bytes"; \ PROBOD_AUTH_COOKIE_SECRET="this-is-a-secure-secret-for-cookie-signing-at-least-32-bytes"; \
PROBOD_AUTH_PASSWORD_PEPPER="this-is-a-secure-pepper-for-password-hashing-at-least-32-bytes"; \ PROBOD_AUTH_PASSWORD_PEPPER="this-is-a-secure-pepper-for-password-hashing-at-least-32-bytes"; \
PROBOD_AUTH_COOKIE_SECURE=false; \ PROBOD_AUTH_COOKIE_SECURE=false; \
PROBOD_OAUTH2_SERVER_SIGNING_KEY="$$($(CAT) $(CFG_DEV_OAUTH2_KEY))"; \ PROBOD_OAUTH2_SERVER_SIGNING_KEY="$$($(CAT) $(CFG_DEV_OAUTH2_KEY))"; \
PROBOD_API_CORS_ALLOWED_ORIGINS="http://localhost:8080,http://localhost:5173,http://localhost:5174"; \ PROBOD_API_CORS_ALLOWED_ORIGINS="http://localhost:8080,http://localhost:5173,http://localhost:5174"; \
PROBOD_PG_ADDR=localhost:5432; \
PROBOD_PG_USERNAME=postgres; \
PROBOD_PG_PASSWORD=postgres; \
PROBOD_PG_DATABASE=probod; \
PROBOD_TRUST_CENTER_HTTP_ADDR=:10080; \
PROBOD_TRUST_CENTER_HTTPS_ADDR=:10443; \
PROBOD_AWS_REGION=us-east-1; \
PROBOD_AWS_BUCKET=probod; \
PROBOD_AWS_ACCESS_KEY_ID=probod; \ PROBOD_AWS_ACCESS_KEY_ID=probod; \
PROBOD_AWS_SECRET_ACCESS_KEY=thisisnotasecret; \ PROBOD_AWS_SECRET_ACCESS_KEY=thisisnotasecret; \
PROBOD_AWS_ENDPOINT=http://127.0.0.1:8333; \ PROBOD_AWS_ENDPOINT=http://127.0.0.1:8333; \
PROBOD_SMTP_ADDR=localhost:1025; \
PROBOD_MAILER_SENDER_EMAIL=no-reply@notification.getprobo.com; \
PROBOD_MAILER_SENDER_NAME=Probo; \
PROBOD_OPENAI_API_KEY=thisisnotasecret; \ PROBOD_OPENAI_API_KEY=thisisnotasecret; \
PROBOD_AGENT_THIRD_PARTY_VETTER_PROVIDER=openai; \ PROBOD_AGENT_THIRD_PARTY_VETTER_PROVIDER=openai; \
PROBOD_AGENT_THIRD_PARTY_VETTER_MODEL_NAME=gpt-4o; \
PROBOD_CHROME_DP_ADDR=localhost:9222; \
PROBOD_ACME_DIRECTORY=https://localhost:14000/dir; \ PROBOD_ACME_DIRECTORY=https://localhost:14000/dir; \
PROBOD_ACME_EMAIL=admin@probo.com; \
PROBOD_ACME_ROOT_CA="$$($(CAT) compose/pebble/certs/rootCA.pem)"; \
if [ -f $(DEV_ENV) ]; then . $(DEV_ENV); fi; \ if [ -f $(DEV_ENV) ]; then . $(DEV_ENV); fi; \
set +a; \ set +a; \
./bin/probod-bootstrap -output $@ ./bin/probod-bootstrap -output $@

View File

@@ -78,7 +78,7 @@ func (b *Builder) Build() (*probodconfig.FullConfig, error) {
Cors: probodconfig.CorsConfig{ Cors: probodconfig.CorsConfig{
AllowedOrigins: b.parseOriginsList(b.resolver.getEnv("PROBOD_API_CORS_ALLOWED_ORIGINS")), AllowedOrigins: b.parseOriginsList(b.resolver.getEnv("PROBOD_API_CORS_ALLOWED_ORIGINS")),
}, },
ExtraHeaderFields: make(map[string]string), ExtraHeaderFields: nil,
GraphQL: probodconfig.GraphQLConfig{ GraphQL: probodconfig.GraphQLConfig{
ParserTokenLimit: b.resolver.getEnvIntOrDefault("PROBOD_API_GRAPHQL_PARSER_TOKEN_LIMIT", 15000), ParserTokenLimit: b.resolver.getEnvIntOrDefault("PROBOD_API_GRAPHQL_PARSER_TOKEN_LIMIT", 15000),
ComplexityLimit: b.resolver.getEnvIntOrDefault("PROBOD_API_GRAPHQL_COMPLEXITY_LIMIT", 2000), ComplexityLimit: b.resolver.getEnvIntOrDefault("PROBOD_API_GRAPHQL_COMPLEXITY_LIMIT", 2000),
@@ -191,83 +191,78 @@ func (b *Builder) Build() (*probodconfig.FullConfig, error) {
ReminderInterval: b.resolver.getEnvIntOrDefault("PROBOD_DOCUMENT_NOTIFICATION_REMINDER_INTERVAL", 86400), ReminderInterval: b.resolver.getEnvIntOrDefault("PROBOD_DOCUMENT_NOTIFICATION_REMINDER_INTERVAL", 86400),
}, },
}, },
Agents: probodconfig.AgentsConfig{ Agents: func() probodconfig.AgentsConfig {
Providers: map[string]probodconfig.LLMProviderConfig{ defaultProvider := b.resolver.getEnvOrDefault("PROBOD_AGENT_DEFAULT_PROVIDER", "openai")
"openai": {
Type: "openai", return probodconfig.AgentsConfig{
APIKey: b.resolver.getEnv("PROBOD_OPENAI_API_KEY"), Providers: b.buildLLMProviders(),
Default: probodconfig.LLMAgentConfig{
Provider: defaultProvider,
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_DEFAULT_MODEL_NAME", "gpt-4o"),
Temperature: new(b.resolver.getEnvFloatOrDefault("PROBOD_AGENT_DEFAULT_TEMPERATURE", 0.1)),
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_DEFAULT_MAX_TOKENS", 4096)),
}, },
"anthropic": { Probo: probodconfig.LLMAgentConfig{
Type: "anthropic", Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_PROBO_PROVIDER", ""),
APIKey: b.resolver.getEnv("PROBOD_ANTHROPIC_API_KEY"), ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_PROBO_MODEL_NAME", ""),
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_PROBO_TEMPERATURE"),
MaxTokens: b.resolver.getEnvIntPtr("PROBOD_AGENT_PROBO_MAX_TOKENS"),
}, },
}, EvidenceDescriber: probodconfig.LLMAgentConfig{
Default: probodconfig.LLMAgentConfig{ Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_EVIDENCE_DESCRIBER_PROVIDER", ""),
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_DEFAULT_PROVIDER", "openai"), ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_EVIDENCE_DESCRIBER_MODEL_NAME", ""),
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_DEFAULT_MODEL_NAME", "gpt-4o"), Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_EVIDENCE_DESCRIBER_TEMPERATURE"),
Temperature: new(b.resolver.getEnvFloatOrDefault("PROBOD_AGENT_DEFAULT_TEMPERATURE", 0.1)), MaxTokens: b.resolver.getEnvIntPtr("PROBOD_AGENT_EVIDENCE_DESCRIBER_MAX_TOKENS"),
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_DEFAULT_MAX_TOKENS", 4096)), },
}, ThirdPartyVetter: probodconfig.LLMAgentConfig{
Probo: probodconfig.LLMAgentConfig{ Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_VETTER_PROVIDER", ""),
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_PROBO_PROVIDER", ""), ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_VETTER_MODEL_NAME", ""),
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_PROBO_MODEL_NAME", ""), Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_THIRD_PARTY_VETTER_TEMPERATURE"),
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_PROBO_TEMPERATURE"), MaxTokens: b.resolver.getEnvIntPtr("PROBOD_AGENT_THIRD_PARTY_VETTER_MAX_TOKENS"),
MaxTokens: b.resolver.getEnvIntPtr("PROBOD_AGENT_PROBO_MAX_TOKENS"), },
}, ThirdPartyDisambiguation: probodconfig.LLMAgentConfig{
EvidenceDescriber: probodconfig.LLMAgentConfig{ Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_PROVIDER", ""),
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_EVIDENCE_DESCRIBER_PROVIDER", ""), ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_MODEL_NAME", ""),
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_EVIDENCE_DESCRIBER_MODEL_NAME", ""), // The disambiguation agent emits a single id plus a
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_EVIDENCE_DESCRIBER_TEMPERATURE"), // short rationale, but the budget must leave headroom
MaxTokens: b.resolver.getEnvIntPtr("PROBOD_AGENT_EVIDENCE_DESCRIBER_MAX_TOKENS"), // for reasoning models whose reasoning tokens count
}, // against max_tokens; too small a budget truncates the
ThirdPartyVetter: probodconfig.LLMAgentConfig{ // JSON.
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_VETTER_PROVIDER", ""), Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_TEMPERATURE"),
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_VETTER_MODEL_NAME", ""), MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_MAX_TOKENS", 4096)),
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_THIRD_PARTY_VETTER_TEMPERATURE"), },
MaxTokens: b.resolver.getEnvIntPtr("PROBOD_AGENT_THIRD_PARTY_VETTER_MAX_TOKENS"), TrackerMapping: probodconfig.LLMAgentConfig{
}, Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_MAPPING_PROVIDER", ""),
ThirdPartyDisambiguation: probodconfig.LLMAgentConfig{ ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_MAPPING_MODEL_NAME", ""),
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_PROVIDER", ""), // The tracker agents emit tiny structured JSON, but
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_MODEL_NAME", ""), // the budget must leave headroom for reasoning
// The disambiguation agent emits a single id plus a // models whose reasoning tokens count against
// short rationale, but the budget must leave headroom // max_tokens; too small a budget truncates the JSON.
// for reasoning models whose reasoning tokens count Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_TRACKER_MAPPING_TEMPERATURE"),
// against max_tokens; too small a budget truncates the MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_TRACKER_MAPPING_MAX_TOKENS", 4096)),
// JSON. },
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_TEMPERATURE"), TrackerEnrichment: probodconfig.LLMAgentConfig{
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_MAX_TOKENS", 4096)), Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_ENRICHMENT_PROVIDER", ""),
}, ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_ENRICHMENT_MODEL_NAME", ""),
TrackerMapping: probodconfig.LLMAgentConfig{ // See the tracker-mapping note: keep ample headroom so
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_MAPPING_PROVIDER", ""), // reasoning models do not truncate the structured JSON.
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_MAPPING_MODEL_NAME", ""), Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_TRACKER_ENRICHMENT_TEMPERATURE"),
// The tracker agents emit tiny structured JSON, but MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_TRACKER_ENRICHMENT_MAX_TOKENS", 4096)),
// the budget must leave headroom for reasoning },
// models whose reasoning tokens count against CommonThirdPartyEnrichment: probodconfig.LLMAgentConfig{
// max_tokens; too small a budget truncates the JSON. Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_PROVIDER", ""),
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_TRACKER_MAPPING_TEMPERATURE"), ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_MODEL_NAME", ""),
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_TRACKER_MAPPING_MAX_TOKENS", 4096)), // Agent B browses pages and emits a moderate structured
}, // output; the budget must leave headroom for reasoning
TrackerEnrichment: probodconfig.LLMAgentConfig{ // models whose reasoning tokens count against max_tokens.
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_ENRICHMENT_PROVIDER", ""), Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_TEMPERATURE"),
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_ENRICHMENT_MODEL_NAME", ""), MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_MAX_TOKENS", 8192)),
// See the tracker-mapping note: keep ample headroom so },
// reasoning models do not truncate the structured JSON. Tools: probodconfig.AgentToolsConfig{
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_TRACKER_ENRICHMENT_TEMPERATURE"), FirecrawlAPIKey: b.resolver.getEnv("PROBOD_FIRECRAWL_API_KEY"),
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_TRACKER_ENRICHMENT_MAX_TOKENS", 4096)), },
}, }
CommonThirdPartyEnrichment: probodconfig.LLMAgentConfig{ }(),
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_PROVIDER", ""),
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_MODEL_NAME", ""),
// Agent B browses pages and emits a moderate structured
// output; the budget must leave headroom for reasoning
// models whose reasoning tokens count against max_tokens.
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_TEMPERATURE"),
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_MAX_TOKENS", 8192)),
},
Tools: probodconfig.AgentToolsConfig{
FirecrawlAPIKey: b.resolver.getEnv("PROBOD_FIRECRAWL_API_KEY"),
},
},
CustomDomains: probodconfig.CustomDomainsConfig{ CustomDomains: probodconfig.CustomDomainsConfig{
RenewalInterval: b.resolver.getEnvIntOrDefault("PROBOD_CUSTOM_DOMAINS_RENEWAL_INTERVAL", 3600), RenewalInterval: b.resolver.getEnvIntOrDefault("PROBOD_CUSTOM_DOMAINS_RENEWAL_INTERVAL", 3600),
ProvisionInterval: b.resolver.getEnvIntOrDefault("PROBOD_CUSTOM_DOMAINS_PROVISION_INTERVAL", 30), ProvisionInterval: b.resolver.getEnvIntOrDefault("PROBOD_CUSTOM_DOMAINS_PROVISION_INTERVAL", 30),
@@ -647,6 +642,30 @@ func (b *Builder) getPgCACertBundle() string {
return b.resolver.getEnv("PROBOD_PG_CA_BUNDLE") return b.resolver.getEnv("PROBOD_PG_CA_BUNDLE")
} }
func (b *Builder) buildLLMProviders() map[string]probodconfig.LLMProviderConfig {
providers := map[string]probodconfig.LLMProviderConfig{}
if apiKey := b.resolver.getEnv("PROBOD_OPENAI_API_KEY"); apiKey != "" {
providers["openai"] = probodconfig.LLMProviderConfig{
Type: "openai",
APIKey: apiKey,
}
}
if apiKey := b.resolver.getEnv("PROBOD_ANTHROPIC_API_KEY"); apiKey != "" {
providers["anthropic"] = probodconfig.LLMProviderConfig{
Type: "anthropic",
APIKey: apiKey,
}
}
if len(providers) == 0 {
return nil
}
return providers
}
func (b *Builder) parseOriginsList(s string) []string { func (b *Builder) parseOriginsList(s string) []string {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
if s == "" { if s == "" {

View File

@@ -214,6 +214,7 @@ func TestBuilder_Build_Defaults(t *testing.T) {
assert.Equal(t, "gpt-4o", cfg.Probod.Agents.Default.ModelName) assert.Equal(t, "gpt-4o", cfg.Probod.Agents.Default.ModelName)
assert.Equal(t, new(0.1), cfg.Probod.Agents.Default.Temperature) assert.Equal(t, new(0.1), cfg.Probod.Agents.Default.Temperature)
assert.Equal(t, new(4096), cfg.Probod.Agents.Default.MaxTokens) assert.Equal(t, new(4096), cfg.Probod.Agents.Default.MaxTokens)
assert.Nil(t, cfg.Probod.Agents.Providers)
// Agents config — per-agent overrides are empty (inherit from default) // Agents config — per-agent overrides are empty (inherit from default)
assert.Empty(t, cfg.Probod.Agents.Probo.Provider) assert.Empty(t, cfg.Probod.Agents.Probo.Provider)
assert.Empty(t, cfg.Probod.Agents.Probo.ModelName) assert.Empty(t, cfg.Probod.Agents.Probo.ModelName)

View File

@@ -37,28 +37,21 @@ func WriteConfig(cfg *probodconfig.FullConfig, path string, format Format) error
return fmt.Errorf("create directory %s: %w", dir, err) return fmt.Errorf("create directory %s: %w", dir, err)
} }
data, err := yaml.Marshal(cfg) var (
if err != nil { data []byte
return fmt.Errorf("marshal config: %w", err) err error
} )
var tree any
if err := yaml.Unmarshal(data, &tree); err != nil {
return fmt.Errorf("unmarshal config: %w", err)
}
pruned := pruneEmptyStrings(tree)
switch format { switch format {
case FormatJSON: case FormatJSON:
data, err = json.MarshalIndent(pruned, "", " ") data, err = json.MarshalIndent(cfg, "", " ")
if err != nil { if err != nil {
return fmt.Errorf("marshal pruned config as json: %w", err) return fmt.Errorf("marshal config as json: %w", err)
} }
case FormatYAML: case FormatYAML:
data, err = yaml.Marshal(pruned) data, err = yaml.Marshal(cfg)
if err != nil { if err != nil {
return fmt.Errorf("marshal pruned config as yaml: %w", err) return fmt.Errorf("marshal config as yaml: %w", err)
} }
default: default:
return fmt.Errorf("unsupported config format: %q", format) return fmt.Errorf("unsupported config format: %q", format)
@@ -70,28 +63,3 @@ func WriteConfig(cfg *probodconfig.FullConfig, path string, format Format) error
return nil return nil
} }
func pruneEmptyStrings(value any) any {
switch v := value.(type) {
case map[string]any:
for key, child := range v {
if s, ok := child.(string); ok && s == "" {
delete(v, key)
continue
}
v[key] = pruneEmptyStrings(child)
}
return v
case []any:
for i, child := range v {
v[i] = pruneEmptyStrings(child)
}
return v
default:
return v
}
}

View File

@@ -86,7 +86,7 @@ func TestWriteConfig_FilePermissions(t *testing.T) {
assert.Equal(t, os.FileMode(0600), info.Mode().Perm()) assert.Equal(t, os.FileMode(0600), info.Mode().Perm())
} }
func TestWriteConfig_DropsEmptyStrings(t *testing.T) { func TestWriteConfig_OmitsOptionalFields(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configPath := filepath.Join(tmpDir, "probod.yml") configPath := filepath.Join(tmpDir, "probod.yml")
@@ -96,9 +96,8 @@ func TestWriteConfig_DropsEmptyStrings(t *testing.T) {
Tracing: probodconfig.TracingConfig{Addr: ""}, Tracing: probodconfig.TracingConfig{Addr: ""},
}, },
Probod: probodconfig.Config{ Probod: probodconfig.Config{
BaseURL: "http://localhost:8080", BaseURL: "http://localhost:8080",
EncryptionKey: "", ChromeDPAddr: "",
ChromeDPAddr: "",
Pg: probodconfig.PgConfig{ Pg: probodconfig.PgConfig{
Addr: "localhost:5432", Addr: "localhost:5432",
Username: "postgres", Username: "postgres",
@@ -124,8 +123,8 @@ func TestWriteConfig_DropsEmptyStrings(t *testing.T) {
require.True(t, ok) require.True(t, ok)
assert.Equal(t, "http://localhost:8080", probod["base-url"]) assert.Equal(t, "http://localhost:8080", probod["base-url"])
assert.NotContains(t, probod, "encryption-key")
assert.NotContains(t, probod, "chrome-dp-addr") assert.NotContains(t, probod, "chrome-dp-addr")
assert.NotContains(t, probod, "esign")
pg, ok := probod["pg"].(map[string]any) pg, ok := probod["pg"].(map[string]any)
require.True(t, ok) require.True(t, ok)
@@ -146,12 +145,168 @@ func TestWriteConfig_DropsEmptyStrings(t *testing.T) {
tracing, ok := unit["tracing"].(map[string]any) tracing, ok := unit["tracing"].(map[string]any)
require.True(t, ok) require.True(t, ok)
assert.NotContains(t, tracing, "addr") assert.NotContains(t, tracing, "addr")
}
loaded := probodconfig.FullConfig{} func TestWriteConfig_OmitsEmptyOptionalBlocks(t *testing.T) {
err = yaml.Unmarshal(data, &loaded) tmpDir := t.TempDir()
configPath := filepath.Join(tmpDir, "probod.yml")
cfg := &probodconfig.FullConfig{
Probod: probodconfig.Config{
BaseURL: "http://localhost:8080",
EncryptionKey: "test-key",
Api: probodconfig.APIConfig{
Addr: ":8080",
},
Auth: probodconfig.AuthConfig{
Cookie: probodconfig.CookieConfig{
Name: "SSID",
Secret: "secret",
},
Password: probodconfig.PasswordConfig{
Pepper: "pepper",
},
},
TrustCenter: probodconfig.TrustCenterConfig{
HTTPAddr: ":80",
},
CustomDomains: probodconfig.CustomDomainsConfig{
RenewalInterval: 3600,
},
Agents: probodconfig.AgentsConfig{
Default: probodconfig.LLMAgentConfig{
Provider: "openai",
ModelName: "gpt-4o",
},
ThirdPartyDisambiguation: probodconfig.LLMAgentConfig{
MaxTokens: new(4096),
},
},
},
}
err := WriteConfig(cfg, configPath, FormatYAML)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, cfg.Probod.BaseURL, loaded.Probod.BaseURL)
assert.Empty(t, loaded.Probod.EncryptionKey) data, err := os.ReadFile(configPath)
require.NoError(t, err)
var tree map[string]any
err = yaml.Unmarshal(data, &tree)
require.NoError(t, err)
probod, ok := tree["probod"].(map[string]any)
require.True(t, ok)
assert.NotContains(t, probod, "esign")
api, ok := probod["api"].(map[string]any)
require.True(t, ok)
assert.NotContains(t, api, "cors")
assert.NotContains(t, api, "proxy-protocol")
assert.NotContains(t, api, "extra-header-fields")
auth, ok := probod["auth"].(map[string]any)
require.True(t, ok)
assert.NotContains(t, auth, "google")
assert.NotContains(t, auth, "microsoft")
customDomains, ok := probod["custom-domains"].(map[string]any)
require.True(t, ok)
assert.NotContains(t, customDomains, "acme")
trustCenter, ok := probod["trust-center"].(map[string]any)
require.True(t, ok)
assert.NotContains(t, trustCenter, "proxy-protocol")
llm, ok := probod["llm"].(map[string]any)
require.True(t, ok)
assert.NotContains(t, llm, "probo")
assert.NotContains(t, llm, "third-party-disambiguation")
assert.NotContains(t, llm, "tools")
}
func TestWriteConfig_OmitsEmptyProxyProtocolAndCorsSlices(t *testing.T) {
tmpDir := t.TempDir()
configPath := filepath.Join(tmpDir, "probod.yml")
cfg := &probodconfig.FullConfig{
Probod: probodconfig.Config{
BaseURL: "http://localhost:8080",
Api: probodconfig.APIConfig{
Addr: ":8080",
ProxyProtocol: probodconfig.ProxyProtocolConfig{
TrustedProxies: []string{},
},
Cors: probodconfig.CorsConfig{
AllowedOrigins: []string{},
},
},
TrustCenter: probodconfig.TrustCenterConfig{
HTTPAddr: ":10080",
ProxyProtocol: probodconfig.ProxyProtocolConfig{
TrustedProxies: make([]string, 0),
},
},
},
}
err := WriteConfig(cfg, configPath, FormatYAML)
require.NoError(t, err)
data, err := os.ReadFile(configPath)
require.NoError(t, err)
var tree map[string]any
err = yaml.Unmarshal(data, &tree)
require.NoError(t, err)
probod, ok := tree["probod"].(map[string]any)
require.True(t, ok)
api, ok := probod["api"].(map[string]any)
require.True(t, ok)
assert.NotContains(t, api, "proxy-protocol")
assert.NotContains(t, api, "cors")
trustCenter, ok := probod["trust-center"].(map[string]any)
require.True(t, ok)
assert.NotContains(t, trustCenter, "proxy-protocol")
}
func TestWriteConfig_OmitsEmptyExtraHeaderFieldsMap(t *testing.T) {
tmpDir := t.TempDir()
configPath := filepath.Join(tmpDir, "probod.yml")
cfg := &probodconfig.FullConfig{
Probod: probodconfig.Config{
BaseURL: "http://localhost:8080",
Api: probodconfig.APIConfig{
Addr: ":8080",
ExtraHeaderFields: map[string]string{},
},
},
}
err := WriteConfig(cfg, configPath, FormatYAML)
require.NoError(t, err)
data, err := os.ReadFile(configPath)
require.NoError(t, err)
var tree map[string]any
err = yaml.Unmarshal(data, &tree)
require.NoError(t, err)
probod, ok := tree["probod"].(map[string]any)
require.True(t, ok)
api, ok := probod["api"].(map[string]any)
require.True(t, ok)
assert.NotContains(t, api, "extra-header-fields")
} }
func TestWriteConfig_CompleteConfig(t *testing.T) { func TestWriteConfig_CompleteConfig(t *testing.T) {
@@ -178,7 +333,6 @@ func TestWriteConfig_CompleteConfig(t *testing.T) {
Cors: probodconfig.CorsConfig{ Cors: probodconfig.CorsConfig{
AllowedOrigins: []string{"http://localhost:8080"}, AllowedOrigins: []string{"http://localhost:8080"},
}, },
ExtraHeaderFields: map[string]string{},
}, },
Pg: probodconfig.PgConfig{ Pg: probodconfig.PgConfig{
Addr: "localhost:5432", Addr: "localhost:5432",
@@ -256,7 +410,7 @@ func TestWriteConfig_JSON(t *testing.T) {
probod, ok := tree["probod"].(map[string]any) probod, ok := tree["probod"].(map[string]any)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, "http://localhost:8080", probod["base-url"]) assert.Equal(t, "http://localhost:8080", probod["base-url"])
assert.NotContains(t, probod, "encryption-key") assert.Equal(t, "", probod["encryption-key"])
var loaded probodconfig.FullConfig var loaded probodconfig.FullConfig

View File

@@ -15,11 +15,19 @@
package probodconfig package probodconfig
type CorsConfig struct { type CorsConfig struct {
AllowedOrigins []string `json:"allowed-origins"` AllowedOrigins []string `json:"allowed-origins,omitzero,omitempty"`
}
func (c CorsConfig) IsZero() bool {
return len(c.AllowedOrigins) == 0
} }
type ProxyProtocolConfig struct { type ProxyProtocolConfig struct {
TrustedProxies []string `json:"trusted-proxies"` TrustedProxies []string `json:"trusted-proxies,omitzero,omitempty"`
}
func (c ProxyProtocolConfig) IsZero() bool {
return len(c.TrustedProxies) == 0
} }
type GraphQLConfig struct { type GraphQLConfig struct {
@@ -30,9 +38,9 @@ type GraphQLConfig struct {
} }
type APIConfig struct { type APIConfig struct {
Addr string `json:"addr"` Addr string `json:"addr,omitempty"`
ProxyProtocol ProxyProtocolConfig `json:"proxy-protocol"` ProxyProtocol ProxyProtocolConfig `json:"proxy-protocol,omitzero"`
Cors CorsConfig `json:"cors"` Cors CorsConfig `json:"cors,omitzero"`
ExtraHeaderFields map[string]string `json:"extra-header-fields"` ExtraHeaderFields map[string]string `json:"extra-header-fields,omitzero,omitempty"`
GraphQL GraphQLConfig `json:"graphql"` GraphQL GraphQLConfig `json:"graphql,omitzero"`
} }

View File

@@ -27,8 +27,8 @@ type AuthConfig struct {
PasswordResetTokenValidity int `json:"password-reset-token-validity"` PasswordResetTokenValidity int `json:"password-reset-token-validity"`
MagicLinkTokenValidity int `json:"magic-link-token-validity"` MagicLinkTokenValidity int `json:"magic-link-token-validity"`
SAML SAMLConfig `json:"saml"` SAML SAMLConfig `json:"saml"`
Google OIDCProviderConfig `json:"google"` Google OIDCProviderConfig `json:"google,omitzero"`
Microsoft OIDCProviderConfig `json:"microsoft"` Microsoft OIDCProviderConfig `json:"microsoft,omitzero"`
OAuth2Server OAuth2ServerConfig `json:"oauth2-server"` OAuth2Server OAuth2ServerConfig `json:"oauth2-server"`
} }
@@ -38,7 +38,7 @@ type OAuth2ServerConfig struct {
RefreshTokenDuration int `json:"refresh-token-duration"` RefreshTokenDuration int `json:"refresh-token-duration"`
AuthorizationCodeDuration int `json:"authorization-code-duration"` AuthorizationCodeDuration int `json:"authorization-code-duration"`
DeviceCodeDuration int `json:"device-code-duration"` DeviceCodeDuration int `json:"device-code-duration"`
CIMDAllowedClientIDs []string `json:"cimd-allowed-client-ids"` CIMDAllowedClientIDs []string `json:"cimd-allowed-client-ids,omitempty"`
} }
type OAuth2SigningKeyConfig struct { type OAuth2SigningKeyConfig struct {
@@ -48,10 +48,10 @@ type OAuth2SigningKeyConfig struct {
} }
type CookieConfig struct { type CookieConfig struct {
Domain string `json:"domain"` Domain string `json:"domain,omitempty"`
Secret string `json:"secret"` Secret string `json:"secret"`
Duration int `json:"duration"` Duration int `json:"duration"`
Name string `json:"name"` Name string `json:"name,omitempty"`
Secure bool `json:"secure"` Secure bool `json:"secure"`
} }

View File

@@ -15,10 +15,10 @@
package probodconfig package probodconfig
type AWSConfig struct { type AWSConfig struct {
Region string `json:"region"` Region string `json:"region,omitempty"`
Bucket string `json:"bucket"` Bucket string `json:"bucket,omitempty"`
AccessKeyID string `json:"access-key-id"` AccessKeyID string `json:"access-key-id,omitempty"`
SecretAccessKey string `json:"secret-access-key"` SecretAccessKey string `json:"secret-access-key,omitempty"`
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint,omitempty"`
UsePathStyle bool `json:"use-path-style"` UsePathStyle bool `json:"use-path-style"`
} }

View File

@@ -35,7 +35,7 @@ type (
// TracingConfig contains tracing configuration. // TracingConfig contains tracing configuration.
TracingConfig struct { TracingConfig struct {
Addr string `json:"addr"` Addr string `json:"addr,omitempty"`
MaxBatchSize int `json:"max-batch-size"` MaxBatchSize int `json:"max-batch-size"`
BatchTimeout int `json:"batch-timeout"` BatchTimeout int `json:"batch-timeout"`
ExportTimeout int `json:"export-timeout"` ExportTimeout int `json:"export-timeout"`
@@ -44,12 +44,12 @@ type (
// ESignConfig contains electronic signature configuration. // ESignConfig contains electronic signature configuration.
ESignConfig struct { ESignConfig struct {
TSAURL string `json:"tsa-url"` TSAURL string `json:"tsa-url,omitempty"`
} }
// Config represents the probod application configuration. // Config represents the probod application configuration.
Config struct { Config struct {
BaseURL string `json:"base-url"` BaseURL string `json:"base-url,omitempty"`
EncryptionKey string `json:"encryption-key"` EncryptionKey string `json:"encryption-key"`
Pg PgConfig `json:"pg"` Pg PgConfig `json:"pg"`
Api APIConfig `json:"api"` Api APIConfig `json:"api"`
@@ -57,7 +57,7 @@ type (
TrustCenter TrustCenterConfig `json:"trust-center"` TrustCenter TrustCenterConfig `json:"trust-center"`
AWS AWSConfig `json:"aws"` AWS AWSConfig `json:"aws"`
Notifications NotificationsConfig `json:"notifications"` Notifications NotificationsConfig `json:"notifications"`
Connectors []ConnectorConfig `json:"connectors"` Connectors []ConnectorConfig `json:"connectors,omitempty"`
Agents AgentsConfig `json:"llm"` Agents AgentsConfig `json:"llm"`
EvidenceDescriber EvidenceDescriberConfig `json:"evidence-describer"` EvidenceDescriber EvidenceDescriberConfig `json:"evidence-describer"`
ThirdPartyVetting ThirdPartyVettingWorkerConfig `json:"third-party-vetting-worker"` ThirdPartyVetting ThirdPartyVettingWorkerConfig `json:"third-party-vetting-worker"`
@@ -66,17 +66,17 @@ type (
CommonPatternEnrichmentWorker CommonPatternEnrichmentWorkerConfig `json:"common-pattern-enrichment-worker"` CommonPatternEnrichmentWorker CommonPatternEnrichmentWorkerConfig `json:"common-pattern-enrichment-worker"`
CommonThirdPartyEnrichmentWorker CommonThirdPartyEnrichmentWorkerConfig `json:"common-third-party-enrichment-worker"` CommonThirdPartyEnrichmentWorker CommonThirdPartyEnrichmentWorkerConfig `json:"common-third-party-enrichment-worker"`
ChromeDPAddr string `json:"chrome-dp-addr"` ChromeDPAddr string `json:"chrome-dp-addr,omitempty"`
CustomDomains CustomDomainsConfig `json:"custom-domains"` CustomDomains CustomDomainsConfig `json:"custom-domains"`
SCIMBridge SCIMBridgeConfig `json:"scim-bridge"` SCIMBridge SCIMBridgeConfig `json:"scim-bridge"`
ESign ESignConfig `json:"esign"` ESign ESignConfig `json:"esign,omitzero"`
Branding bool `json:"branding"` Branding bool `json:"branding"`
} }
// TrustCenterConfig contains trust center server configuration. // TrustCenterConfig contains trust center server configuration.
TrustCenterConfig struct { TrustCenterConfig struct {
HTTPAddr string `json:"http-addr"` HTTPAddr string `json:"http-addr,omitempty"`
HTTPSAddr string `json:"https-addr"` HTTPSAddr string `json:"https-addr,omitempty"`
ProxyProtocol ProxyProtocolConfig `json:"proxy-protocol"` ProxyProtocol ProxyProtocolConfig `json:"proxy-protocol,omitzero"`
} }
) )

View File

@@ -17,16 +17,16 @@ package probodconfig
type CustomDomainsConfig struct { type CustomDomainsConfig struct {
RenewalInterval int `json:"renewal-interval"` RenewalInterval int `json:"renewal-interval"`
ProvisionInterval int `json:"provision-interval"` ProvisionInterval int `json:"provision-interval"`
ResolverAddr string `json:"resolver-addr"` ResolverAddr string `json:"resolver-addr,omitempty"`
CnameTarget string `json:"cname-target"` CnameTarget string `json:"cname-target"`
CAAIssuerDomain string `json:"caa-issuer-domain"` CAAIssuerDomain string `json:"caa-issuer-domain"`
ACME ACMEConfig `json:"acme"` ACME ACMEConfig `json:"acme,omitzero"`
} }
type ACMEConfig struct { type ACMEConfig struct {
Directory string `json:"directory"` Directory string `json:"directory,omitempty"`
Email string `json:"email"` Email string `json:"email,omitempty"`
KeyType string `json:"key-type"` KeyType string `json:"key-type,omitempty"`
AccountKey string `json:"account-key"` AccountKey string `json:"account-key,omitempty"`
RootCA string `json:"root-ca"` RootCA string `json:"root-ca,omitempty"`
} }

View File

@@ -18,17 +18,17 @@ type (
// LLMProviderConfig holds authentication and connection settings for an // LLMProviderConfig holds authentication and connection settings for an
// LLM provider (e.g. OpenAI, Anthropic). // LLM provider (e.g. OpenAI, Anthropic).
LLMProviderConfig struct { LLMProviderConfig struct {
Type string `json:"type"` // "openai", "anthropic", "bedrock" Type string `json:"type"` // "openai", "anthropic", "bedrock"
APIKey string `json:"api-key"` // for OpenAI and Anthropic APIKey string `json:"api-key,omitempty"` // for OpenAI and Anthropic
} }
// LLMAgentConfig holds model parameters for a single agent. Provider // LLMAgentConfig holds model parameters for a single agent. Provider
// references one of the keys in AgentsConfig.Providers. // references one of the keys in AgentsConfig.Providers.
LLMAgentConfig struct { LLMAgentConfig struct {
Provider string `json:"provider"` // key into AgentsConfig.Providers Provider string `json:"provider,omitempty"` // key into AgentsConfig.Providers
ModelName string `json:"model-name"` ModelName string `json:"model-name,omitempty"`
Temperature *float64 `json:"temperature"` Temperature *float64 `json:"temperature,omitempty"`
MaxTokens *int `json:"max-tokens"` MaxTokens *int `json:"max-tokens,omitempty"`
} }
// EvidenceDescriberConfig holds worker-side tuning for the evidence // EvidenceDescriberConfig holds worker-side tuning for the evidence
@@ -95,26 +95,38 @@ type (
// AgentToolsConfig holds API keys and settings for external tools // AgentToolsConfig holds API keys and settings for external tools
// that agents can use (web search, scraping, etc.). // that agents can use (web search, scraping, etc.).
AgentToolsConfig struct { AgentToolsConfig struct {
FirecrawlAPIKey string `json:"firecrawl-api-key"` FirecrawlAPIKey string `json:"firecrawl-api-key,omitempty"`
} }
// AgentsConfig groups LLM provider credentials and per-agent model // AgentsConfig groups LLM provider credentials and per-agent model
// settings. Default is used as a fallback when an agent-specific field // settings. Default is used as a fallback when an agent-specific field
// is zero-valued. // is zero-valued.
AgentsConfig struct { AgentsConfig struct {
Providers map[string]LLMProviderConfig `json:"providers"` Providers map[string]LLMProviderConfig `json:"providers,omitempty"`
Default LLMAgentConfig `json:"defaults"` Default LLMAgentConfig `json:"defaults"`
Probo LLMAgentConfig `json:"probo"` Probo LLMAgentConfig `json:"probo,omitzero"`
EvidenceDescriber LLMAgentConfig `json:"evidence-describer"` EvidenceDescriber LLMAgentConfig `json:"evidence-describer,omitzero"`
ThirdPartyVetter LLMAgentConfig `json:"third-party-vetter"` ThirdPartyVetter LLMAgentConfig `json:"third-party-vetter,omitzero"`
ThirdPartyDisambiguation LLMAgentConfig `json:"third-party-disambiguation"` ThirdPartyDisambiguation LLMAgentConfig `json:"third-party-disambiguation,omitzero"`
TrackerMapping LLMAgentConfig `json:"tracker-mapping"` TrackerMapping LLMAgentConfig `json:"tracker-mapping,omitzero"`
TrackerEnrichment LLMAgentConfig `json:"tracker-enrichment"` TrackerEnrichment LLMAgentConfig `json:"tracker-enrichment,omitzero"`
CommonThirdPartyEnrichment LLMAgentConfig `json:"common-third-party-enrichment"` CommonThirdPartyEnrichment LLMAgentConfig `json:"common-third-party-enrichment,omitzero"`
Tools AgentToolsConfig `json:"tools"` Tools AgentToolsConfig `json:"tools,omitzero"`
} }
) )
func (c LLMProviderConfig) IsZero() bool {
return c.APIKey == ""
}
func (c LLMAgentConfig) IsZero() bool {
return c.Provider == "" && c.ModelName == ""
}
func (c AgentToolsConfig) IsZero() bool {
return c.FirecrawlAPIKey == ""
}
// ResolveAgent returns a fully populated LLMAgentConfig by filling in // ResolveAgent returns a fully populated LLMAgentConfig by filling in
// zero-valued fields from the default config. // zero-valued fields from the default config.
func (c *AgentsConfig) ResolveAgent(agent LLMAgentConfig) LLMAgentConfig { func (c *AgentsConfig) ResolveAgent(agent LLMAgentConfig) LLMAgentConfig {

View File

@@ -16,15 +16,15 @@ package probodconfig
type MailerConfig struct { type MailerConfig struct {
MailerInterval int `json:"mailer-interval"` MailerInterval int `json:"mailer-interval"`
SenderName string `json:"sender-name"` SenderName string `json:"sender-name,omitempty"`
SenderEmail string `json:"sender-email"` SenderEmail string `json:"sender-email,omitempty"`
SMTP SMTPConfig `json:"smtp"` SMTP SMTPConfig `json:"smtp,omitzero"`
} }
type SMTPConfig struct { type SMTPConfig struct {
Addr string `json:"addr"` Addr string `json:"addr,omitempty"`
User string `json:"user"` User string `json:"user,omitempty"`
Password string `json:"password"` Password string `json:"password,omitempty"`
TLSRequired bool `json:"tls-required"` TLSRequired bool `json:"tls-required"`
HelloName string `json:"hello-name"` HelloName string `json:"hello-name,omitempty"`
} }

View File

@@ -15,7 +15,11 @@
package probodconfig package probodconfig
type OIDCProviderConfig struct { type OIDCProviderConfig struct {
ClientID string `json:"client-id"` ClientID string `json:"client-id,omitempty"`
ClientSecret string `json:"client-secret"` ClientSecret string `json:"client-secret,omitempty"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled,omitempty"`
}
func (c OIDCProviderConfig) IsZero() bool {
return c.ClientID == "" && c.ClientSecret == ""
} }

View File

@@ -23,17 +23,17 @@ import (
) )
type PgConfig struct { type PgConfig struct {
Addr string `json:"addr"` Addr string `json:"addr,omitempty"`
Username string `json:"username"` Username string `json:"username,omitempty"`
Password string `json:"password"` Password string `json:"password,omitempty"`
Database string `json:"database"` Database string `json:"database,omitempty"`
PoolSize int32 `json:"pool-size"` PoolSize int32 `json:"pool-size"`
MinPoolSize int32 `json:"min-pool-size"` MinPoolSize int32 `json:"min-pool-size"`
MaxConnIdleTimeSeconds int `json:"max-conn-idle-time-seconds"` MaxConnIdleTimeSeconds int `json:"max-conn-idle-time-seconds"`
MaxConnLifetimeSeconds int `json:"max-conn-lifetime-seconds"` MaxConnLifetimeSeconds int `json:"max-conn-lifetime-seconds"`
MaxConnLifetimeJitterSeconds int `json:"max-conn-lifetime-jitter-seconds"` MaxConnLifetimeJitterSeconds int `json:"max-conn-lifetime-jitter-seconds"`
HealthCheckPeriodSeconds int `json:"health-check-period-seconds"` HealthCheckPeriodSeconds int `json:"health-check-period-seconds"`
CACertBundle string `json:"ca-cert-bundle"` CACertBundle string `json:"ca-cert-bundle,omitempty"`
Debug bool `json:"debug"` Debug bool `json:"debug"`
} }

View File

@@ -24,7 +24,7 @@ type SAMLConfig struct {
Certificate string `json:"certificate"` Certificate string `json:"certificate"`
PrivateKey string `json:"private-key"` PrivateKey string `json:"private-key"`
DomainVerificationIntervalSeconds int `json:"domain-verification-interval-seconds"` DomainVerificationIntervalSeconds int `json:"domain-verification-interval-seconds"`
DomainVerificationResolverAddr string `json:"domain-verification-resolver-addr"` DomainVerificationResolverAddr string `json:"domain-verification-resolver-addr,omitempty"`
} }
func (c SAMLConfig) SessionDurationTime() time.Duration { func (c SAMLConfig) SessionDurationTime() time.Duration {

View File

@@ -16,5 +16,5 @@ package probodconfig
type SlackConfig struct { type SlackConfig struct {
SenderInterval int `json:"sender-interval"` SenderInterval int `json:"sender-interval"`
SigningSecret string `json:"signing-secret"` SigningSecret string `json:"signing-secret,omitempty"`
} }