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

@@ -15,11 +15,19 @@
package probodconfig
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 {
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 {
@@ -30,9 +38,9 @@ type GraphQLConfig struct {
}
type APIConfig struct {
Addr string `json:"addr"`
ProxyProtocol ProxyProtocolConfig `json:"proxy-protocol"`
Cors CorsConfig `json:"cors"`
ExtraHeaderFields map[string]string `json:"extra-header-fields"`
GraphQL GraphQLConfig `json:"graphql"`
Addr string `json:"addr,omitempty"`
ProxyProtocol ProxyProtocolConfig `json:"proxy-protocol,omitzero"`
Cors CorsConfig `json:"cors,omitzero"`
ExtraHeaderFields map[string]string `json:"extra-header-fields,omitzero,omitempty"`
GraphQL GraphQLConfig `json:"graphql,omitzero"`
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,11 @@
package probodconfig
type OIDCProviderConfig struct {
ClientID string `json:"client-id"`
ClientSecret string `json:"client-secret"`
Enabled bool `json:"enabled"`
ClientID string `json:"client-id,omitempty"`
ClientSecret string `json:"client-secret,omitempty"`
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 {
Addr string `json:"addr"`
Username string `json:"username"`
Password string `json:"password"`
Database string `json:"database"`
Addr string `json:"addr,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Database string `json:"database,omitempty"`
PoolSize int32 `json:"pool-size"`
MinPoolSize int32 `json:"min-pool-size"`
MaxConnIdleTimeSeconds int `json:"max-conn-idle-time-seconds"`
MaxConnLifetimeSeconds int `json:"max-conn-lifetime-seconds"`
MaxConnLifetimeJitterSeconds int `json:"max-conn-lifetime-jitter-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"`
}

View File

@@ -24,7 +24,7 @@ type SAMLConfig struct {
Certificate string `json:"certificate"`
PrivateKey string `json:"private-key"`
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 {

View File

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