add tool to gitea
Build and Deploy / build-and-deploy (push) Failing after 20s

This commit is contained in:
2026-06-05 02:34:00 +10:00
commit 532b912ffb
25 changed files with 4981 additions and 0 deletions
+687
View File
@@ -0,0 +1,687 @@
package config
// ------------------------------------------------------------
// Default values — single source of truth for every default
// referenced in the schema. Change a default here and it
// propagates everywhere automatically.
// ------------------------------------------------------------
const (
DefaultTLSIssuer = "letsencrypt-prod"
DefaultIngressClass = "nginx"
DefaultCNPGHost = "cnpg-main-rw.default.svc.cluster.local"
DefaultNamespacePattern = "${env}"
DefaultImagePullPolicy = "Always"
DefaultServiceType = "ClusterIP"
DefaultDockerfile = "Dockerfile"
DefaultImageTag = "latest"
DefaultPullSecret = "regcred"
DefaultHealthCheckPath = "/healthcheck"
DefaultRegistryURL = "registry.natelubitz.com"
DefaultPort = 3000
DefaultReplicas = 1
DefaultInitialDelaySecs = 15
DefaultPeriodSecs = 10
DefaultTimeoutSecs = 5
DefaultFailureThreshold = 3
DefaultDeleteGraceSecs = 300
DefaultSuccessfulJobsHist = 3
DefaultFailedJobsHist = 1
DefaultCacheProvider = "valkey"
DefaultCacheMode = "standalone"
DefaultStorageProvider = "minio"
DefaultStorageMode = "standalone"
DefaultQueueProvider = "nats"
DefaultSearchProvider = "meilisearch"
DefaultMonProvider = "prometheus"
DefaultMetricsPath = "/metrics"
DefaultRestartPolicy = "OnFailure"
DefaultConcurrencyPolicy = "Forbid"
)
// boolPtr / intPtr are helpers for pointer defaults.
func boolPtr(b bool) *bool { return &b }
func intPtr(i int) *int { return &i }
// isEnabled returns true if the InfraBase has no explicit Enabled
// value set (nil = inherits root default, which is true when the
// block exists) or if Enabled is explicitly true.
func isEnabled(b *bool) bool {
return b == nil || *b
}
// ------------------------------------------------------------
// ApplyDefaults fills in every zero/nil value in the config
// with the canonical default. Called once after unmarshalling,
// before any environment resolution.
// ------------------------------------------------------------
func ApplyDefaults(cfg *KforgeConfig) {
applyClusterDefaults(&cfg.Cluster)
applyRegistryDefaults(&cfg.Registry, &cfg.Meta)
applyDeploymentDefaults(&cfg.Defaults)
applyRootInfraDefaults(&cfg.Infrastructure)
}
func applyClusterDefaults(c *ClusterConfig) {
if c.TLSIssuer == "" {
c.TLSIssuer = DefaultTLSIssuer
}
if c.IngressClass == "" {
c.IngressClass = DefaultIngressClass
}
if c.CNPG.Host == "" {
c.CNPG.Host = DefaultCNPGHost
}
if c.NamespacePattern == "" {
c.NamespacePattern = DefaultNamespacePattern
}
}
func applyRegistryDefaults(r *RegistryConfig, m *MetaConfig) {
if r.URL == "" {
r.URL = DefaultRegistryURL
}
if r.PullSecret == "" {
r.PullSecret = DefaultPullSecret
}
if r.Repository == "" {
r.Repository = "${tenant}/${name}"
}
}
func applyDeploymentDefaults(d *DefaultsConfig) {
if d.ImagePullPolicy == "" {
d.ImagePullPolicy = DefaultImagePullPolicy
}
if d.Replicas == nil {
d.Replicas = intPtr(DefaultReplicas)
}
if d.Port == nil {
d.Port = intPtr(DefaultPort)
}
if d.ServiceType == "" {
d.ServiceType = DefaultServiceType
}
if d.Dockerfile == "" {
d.Dockerfile = DefaultDockerfile
}
applyHealthCheckDefaults(&d.HealthCheck, d.Port)
applyResourceDefaults(&d.Resources)
}
func applyHealthCheckDefaults(h *HealthCheckConfig, defaultPort *int) {
if h.Path == "" {
h.Path = DefaultHealthCheckPath
}
if h.Port == nil {
h.Port = defaultPort
}
if h.InitialDelaySeconds == 0 {
h.InitialDelaySeconds = DefaultInitialDelaySecs
}
if h.PeriodSeconds == 0 {
h.PeriodSeconds = DefaultPeriodSecs
}
if h.TimeoutSeconds == 0 {
h.TimeoutSeconds = DefaultTimeoutSecs
}
if h.FailureThreshold == 0 {
h.FailureThreshold = DefaultFailureThreshold
}
if h.Liveness == nil {
h.Liveness = boolPtr(true)
}
if h.Readiness == nil {
h.Readiness = boolPtr(true)
}
}
func applyResourceDefaults(r *ResourceConfig) {
if r.Requests.CPU == "" {
r.Requests.CPU = "100m"
}
if r.Requests.Memory == "" {
r.Requests.Memory = "128Mi"
}
if r.Limits.CPU == "" {
r.Limits.CPU = "500m"
}
if r.Limits.Memory == "" {
r.Limits.Memory = "512Mi"
}
}
// applyRootInfraDefaults sets provider/mode defaults on the root
// infrastructure block. The enabled flag is handled by the merge
// step: if a block exists at root with no explicit enabled:false,
// it is considered enabled.
func applyRootInfraDefaults(infra *InfrastructureConfig) {
if infra.Cache != nil {
if infra.Cache.Provider == "" {
infra.Cache.Provider = DefaultCacheProvider
}
if infra.Cache.Mode == "" {
infra.Cache.Mode = DefaultCacheMode
}
if infra.Cache.Replicas == nil {
infra.Cache.Replicas = intPtr(1)
}
}
if infra.Storage != nil {
if infra.Storage.Provider == "" {
infra.Storage.Provider = DefaultStorageProvider
}
if infra.Storage.Mode == "" {
infra.Storage.Mode = DefaultStorageMode
}
}
if infra.Queue != nil {
if infra.Queue.Provider == "" {
infra.Queue.Provider = DefaultQueueProvider
}
}
if infra.Search != nil {
if infra.Search.Provider == "" {
infra.Search.Provider = DefaultSearchProvider
}
}
if infra.Monitoring != nil {
if infra.Monitoring.Provider == "" {
infra.Monitoring.Provider = DefaultMonProvider
}
if infra.Monitoring.MetricsPath == "" {
infra.Monitoring.MetricsPath = DefaultMetricsPath
}
}
}
// ------------------------------------------------------------
// ResolveEnvironment produces a fully-merged, fully-defaulted
// ResolvedEnvironment for a given environment key.
//
// Merge order (last wins):
// 1. Compiled defaults (from ApplyDefaults)
// 2. Root infrastructure block
// 3. Per-environment overrides
// ------------------------------------------------------------
// ResolvedEnvironment is the flattened, ready-to-generate view
// of a single environment. Generators consume this type only —
// they never touch raw config or merge logic.
type ResolvedEnvironment struct {
// Identity
EnvKey string
EnvPrefix string
Namespace string
FullName string // ${env_prefix}-${tenant}-${name}
// Deployment
Image string
ImagePullPolicy string
ImagePullSecret string
Replicas int
Port int
ServiceType string
HealthCheck HealthCheckConfig
Resources ResourceConfig
EnvVars []EnvVarConfig
// Ingress
Ingress IngressConfig
// Infrastructure (merged root + env override)
Infrastructure ResolvedInfrastructure
// Cron jobs
CronJobs []ResolvedCronJob
// Cluster-level settings (carried through for generators)
TLSIssuer string
IngressClass string
CNPGHost string
// Lifecycle
Lifecycle LifecycleConfig
}
// ResolvedInfrastructure holds the fully-merged infra state for
// one environment. Each field is nil if the service is disabled.
type ResolvedInfrastructure struct {
Database *DatabaseInfraConfig
Cache *CacheInfraConfig
Storage *StorageInfraConfig
Queue *QueueInfraConfig
Search *SearchInfraConfig
Monitoring *MonitoringInfraConfig
}
// ResolvedCronJob is a fully-defaulted cron job ready for the
// CronJob manifest generator.
type ResolvedCronJob struct {
CronJobConfig
Image string
EnvVars []EnvVarConfig // merged: deployment vars + job-specific vars
}
// ResolveEnvironment merges root config + env overrides into a
// single ResolvedEnvironment. cfg must have had ApplyDefaults
// called on it first.
func ResolveEnvironment(cfg *KforgeConfig, envKey string) (ResolvedEnvironment, error) {
env, ok := cfg.Environments[envKey]
if !ok {
return ResolvedEnvironment{}, &UnknownEnvironmentError{Key: envKey}
}
prefix := resolveEnvPrefix(envKey, env.EnvPrefix)
namespace := resolveNamespace(envKey, env.Namespace, cfg.Cluster.NamespacePattern)
fullName := resolveFullName(cfg, prefix, env)
registry := resolveRegistry(cfg, env)
imageTag := env.ImageTag
if imageTag == "" {
imageTag = DefaultImageTag
}
image := registry.URL + "/" + registry.Repository + ":" + imageTag
replicas := *cfg.Defaults.Replicas
if env.Replicas != nil {
replicas = *env.Replicas
}
port := *cfg.Defaults.Port
hc := cfg.Defaults.HealthCheck
if hc.Port == nil || *hc.Port == 0 {
hc.Port = intPtr(port)
}
envVars := mergeEnvVars(cfg.Defaults.EnvVars, env.EnvVars)
infra := mergeInfrastructure(&cfg.Infrastructure, &env.Infrastructure)
cronJobs := resolveCronJobs(env.CronJobs, image, envVars, cfg.Defaults.Resources)
lifecycle := env.Lifecycle
if lifecycle.DeleteGraceSeconds == 0 {
lifecycle.DeleteGraceSeconds = DefaultDeleteGraceSecs
}
auth := env.Ingress.Auth
if auth.SecretName == "" {
auth.SecretName = fullName + "-basic-auth"
}
ingress := IngressConfig{
Hosts: env.Ingress.Hosts,
Auth: auth,
}
return ResolvedEnvironment{
EnvKey: envKey,
EnvPrefix: prefix,
Namespace: namespace,
FullName: fullName,
Image: image,
ImagePullPolicy: cfg.Defaults.ImagePullPolicy,
ImagePullSecret: registry.PullSecret,
Replicas: replicas,
Port: port,
ServiceType: cfg.Defaults.ServiceType,
HealthCheck: hc,
Resources: cfg.Defaults.Resources,
EnvVars: envVars,
Ingress: ingress,
Infrastructure: infra,
CronJobs: cronJobs,
TLSIssuer: cfg.Cluster.TLSIssuer,
IngressClass: cfg.Cluster.IngressClass,
CNPGHost: cfg.Cluster.CNPG.Host,
Lifecycle: lifecycle,
}, nil
}
// ------------------------------------------------------------
// Internal resolution helpers
// ------------------------------------------------------------
func resolveEnvPrefix(envKey string, override *string) string {
if override != nil && *override != "" {
return *override
}
// Default: first 4 chars of envKey, or full key if shorter.
if len(envKey) <= 4 {
return envKey
}
return envKey[:4]
}
func resolveNamespace(envKey, explicit, pattern string) string {
if explicit != "" {
return explicit
}
// Apply the namespace pattern (simple token replace here;
// full interpolation runs later via the interpolate package).
result := pattern
if result == "" {
return envKey
}
return result
}
func resolveFullName(cfg *KforgeConfig, prefix string, env EnvironmentConfig) string {
if cfg.Meta.NameOverride != nil && *cfg.Meta.NameOverride != "" {
return *cfg.Meta.NameOverride
}
return prefix + "-" + cfg.Meta.Tenant + "-" + cfg.Meta.Name
}
func resolveRegistry(cfg *KforgeConfig, env EnvironmentConfig) RegistryConfig {
base := cfg.Registry
// Resolve the repository token now that we have meta values.
if base.Repository == "${tenant}/${name}" || base.Repository == "" {
base.Repository = cfg.Meta.Tenant + "/" + cfg.Meta.Name
}
if env.Registry != nil {
r := *env.Registry
if r.URL == "" {
r.URL = base.URL
}
if r.Repository == "" {
r.Repository = base.Repository
}
if r.PullSecret == "" {
r.PullSecret = base.PullSecret
}
return r
}
return base
}
// MergeEnvVars is the exported form of mergeEnvVars, used by the
// generate command to inject infrastructure env vars into deployments.
func MergeEnvVars(base, override []EnvVarConfig) []EnvVarConfig {
return mergeEnvVars(base, override)
}
// mergeEnvVars combines base and override slices. If an env var
// with the same Name appears in both, the override wins.
func mergeEnvVars(base, override []EnvVarConfig) []EnvVarConfig {
merged := make([]EnvVarConfig, 0, len(base)+len(override))
seen := make(map[string]int) // name → index in merged
for _, v := range base {
seen[v.Name] = len(merged)
merged = append(merged, v)
}
for _, v := range override {
if idx, ok := seen[v.Name]; ok {
merged[idx] = v // override
} else {
seen[v.Name] = len(merged)
merged = append(merged, v)
}
}
return merged
}
// mergeInfrastructure performs a shallow merge of root infra
// defaults with per-environment overrides.
//
// Rules:
// - If root has a service block with no explicit enabled:false,
// it is enabled in every environment.
// - An env block with enabled:false disables the service.
// - An env block with partial fields overrides only those fields;
// everything else inherits from root.
// - If root has no block for a service, env can still enable it
// by providing its own block (enabled defaults to true if present).
func mergeInfrastructure(root, env *InfrastructureConfig) ResolvedInfrastructure {
return ResolvedInfrastructure{
Database: mergeDatabase(root.Database, env.Database),
Cache: mergeCache(root.Cache, env.Cache),
Storage: mergeStorage(root.Storage, env.Storage),
Queue: mergeQueue(root.Queue, env.Queue),
Search: mergeSearch(root.Search, env.Search),
Monitoring: mergeMonitoring(root.Monitoring, env.Monitoring),
}
}
func mergeDatabase(root, env *DatabaseInfraConfig) *DatabaseInfraConfig {
if root == nil && env == nil {
return nil
}
merged := &DatabaseInfraConfig{}
if root != nil {
*merged = *root
}
if env != nil {
if env.Enabled != nil {
merged.Enabled = env.Enabled
}
if env.Provider != "" {
merged.Provider = env.Provider
}
if env.DatabaseName != "" {
merged.DatabaseName = env.DatabaseName
}
}
if !isEnabled(merged.Enabled) {
return nil
}
if merged.Provider == "" {
merged.Provider = "cnpg"
}
return merged
}
func mergeCache(root, env *CacheInfraConfig) *CacheInfraConfig {
if root == nil && env == nil {
return nil
}
merged := &CacheInfraConfig{}
if root != nil {
*merged = *root
}
if env != nil {
if env.Enabled != nil {
merged.Enabled = env.Enabled
}
if env.Provider != "" {
merged.Provider = env.Provider
}
if env.Mode != "" {
merged.Mode = env.Mode
}
if env.Replicas != nil {
merged.Replicas = env.Replicas
}
}
if !isEnabled(merged.Enabled) {
return nil
}
if merged.Provider == "" {
merged.Provider = DefaultCacheProvider
}
if merged.Mode == "" {
merged.Mode = DefaultCacheMode
}
if merged.Replicas == nil {
merged.Replicas = intPtr(1)
}
return merged
}
func mergeStorage(root, env *StorageInfraConfig) *StorageInfraConfig {
if root == nil && env == nil {
return nil
}
merged := &StorageInfraConfig{}
if root != nil {
*merged = *root
}
if env != nil {
if env.Enabled != nil {
merged.Enabled = env.Enabled
}
if env.Provider != "" {
merged.Provider = env.Provider
}
if env.Mode != "" {
merged.Mode = env.Mode
}
}
if !isEnabled(merged.Enabled) {
return nil
}
if merged.Provider == "" {
merged.Provider = DefaultStorageProvider
}
if merged.Mode == "" {
merged.Mode = DefaultStorageMode
}
return merged
}
func mergeQueue(root, env *QueueInfraConfig) *QueueInfraConfig {
if root == nil && env == nil {
return nil
}
merged := &QueueInfraConfig{}
if root != nil {
*merged = *root
}
if env != nil {
if env.Enabled != nil {
merged.Enabled = env.Enabled
}
if env.Provider != "" {
merged.Provider = env.Provider
}
}
if !isEnabled(merged.Enabled) {
return nil
}
if merged.Provider == "" {
merged.Provider = DefaultQueueProvider
}
return merged
}
func mergeSearch(root, env *SearchInfraConfig) *SearchInfraConfig {
if root == nil && env == nil {
return nil
}
merged := &SearchInfraConfig{}
if root != nil {
*merged = *root
}
if env != nil {
if env.Enabled != nil {
merged.Enabled = env.Enabled
}
if env.Provider != "" {
merged.Provider = env.Provider
}
}
if !isEnabled(merged.Enabled) {
return nil
}
if merged.Provider == "" {
merged.Provider = DefaultSearchProvider
}
return merged
}
func mergeMonitoring(root, env *MonitoringInfraConfig) *MonitoringInfraConfig {
if root == nil && env == nil {
return nil
}
merged := &MonitoringInfraConfig{}
if root != nil {
*merged = *root
}
if env != nil {
if env.Enabled != nil {
merged.Enabled = env.Enabled
}
if env.Provider != "" {
merged.Provider = env.Provider
}
if env.MetricsPath != "" {
merged.MetricsPath = env.MetricsPath
}
if env.MetricsPort != nil {
merged.MetricsPort = env.MetricsPort
}
}
if !isEnabled(merged.Enabled) {
return nil
}
if merged.Provider == "" {
merged.Provider = DefaultMonProvider
}
if merged.MetricsPath == "" {
merged.MetricsPath = DefaultMetricsPath
}
return merged
}
// resolveCronJobs applies defaults to each cron job and merges
// env vars from the parent deployment.
func resolveCronJobs(jobs []CronJobConfig, deploymentImage string, deploymentEnvVars []EnvVarConfig, defaultResources ResourceConfig) []ResolvedCronJob {
resolved := make([]ResolvedCronJob, 0, len(jobs))
for _, job := range jobs {
r := ResolvedCronJob{CronJobConfig: job}
// Image
if job.ImageOverride != nil && *job.ImageOverride != "" {
r.Image = *job.ImageOverride
} else {
r.Image = deploymentImage
}
// Env vars: deployment vars first, then job-specific (with merge)
inheritEnv := job.InheritEnv == nil || *job.InheritEnv
if inheritEnv {
r.EnvVars = mergeEnvVars(deploymentEnvVars, job.EnvVars)
} else {
r.EnvVars = job.EnvVars
}
// Resources
if job.Resources == nil {
r.Resources = &defaultResources
}
// Tuning defaults
if r.RestartPolicy == "" {
r.RestartPolicy = DefaultRestartPolicy
}
if r.ConcurrencyPolicy == "" {
r.ConcurrencyPolicy = DefaultConcurrencyPolicy
}
if r.SuccessfulJobsHistoryLimit == nil {
r.SuccessfulJobsHistoryLimit = intPtr(DefaultSuccessfulJobsHist)
}
if r.FailedJobsHistoryLimit == nil {
r.FailedJobsHistoryLimit = intPtr(DefaultFailedJobsHist)
}
resolved = append(resolved, r)
}
return resolved
}
// ------------------------------------------------------------
// Errors
// ------------------------------------------------------------
type UnknownEnvironmentError struct {
Key string
}
func (e *UnknownEnvironmentError) Error() string {
return "unknown environment: " + e.Key
}
+107
View File
@@ -0,0 +1,107 @@
package config
import (
"fmt"
"os"
"gopkg.in/yaml.v3"
)
// Load reads a kforge.yml file from disk, applies all defaults,
// and returns the parsed config ready for environment resolution.
func Load(path string) (*KforgeConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading %s: %w", path, err)
}
return LoadBytes(data)
}
// LoadBytes parses kforge.yml from a byte slice. Useful in tests.
func LoadBytes(data []byte) (*KforgeConfig, error) {
cfg := &KforgeConfig{}
if err := yaml.Unmarshal(data, cfg); err != nil {
return nil, fmt.Errorf("parsing kforge.yml: %w", err)
}
if err := validate(cfg); err != nil {
return nil, err
}
ApplyDefaults(cfg)
return cfg, nil
}
// validate performs structural validation before defaults are
// applied. Returns the first error found.
func validate(cfg *KforgeConfig) error {
if cfg.Meta.Name == "" {
return fmt.Errorf("meta.name is required")
}
if cfg.Meta.Tenant == "" {
return fmt.Errorf("meta.tenant is required")
}
if len(cfg.Environments) == 0 {
return fmt.Errorf("at least one environment must be defined")
}
for envKey, env := range cfg.Environments {
if len(env.Ingress.Hosts) == 0 {
return fmt.Errorf("environments.%s: at least one ingress host is required", envKey)
}
for _, host := range env.Ingress.Hosts {
if host.Hostname == "" {
return fmt.Errorf("environments.%s: ingress host hostname cannot be empty", envKey)
}
}
for _, job := range env.CronJobs {
if job.Name == "" {
return fmt.Errorf("environments.%s: cron job is missing a name", envKey)
}
if job.Schedule == "" {
return fmt.Errorf("environments.%s.cron_jobs.%s: schedule is required", envKey, job.Name)
}
if len(job.Command) == 0 {
return fmt.Errorf("environments.%s.cron_jobs.%s: command is required", envKey, job.Name)
}
}
for _, v := range env.EnvVars {
if err := validateEnvVar(envKey, v); err != nil {
return err
}
}
}
for _, v := range cfg.Defaults.EnvVars {
if err := validateEnvVar("defaults", v); err != nil {
return err
}
}
return nil
}
func validateEnvVar(context string, v EnvVarConfig) error {
if v.Name == "" {
return fmt.Errorf("%s: env var is missing a name", context)
}
switch v.Type {
case EnvVarTypePlain, "":
// value can technically be empty (empty string env var is valid)
case EnvVarTypeSecretRef:
if v.SecretName == "" || v.SecretKey == "" {
return fmt.Errorf("%s: env var %q (secret_ref) requires secret_name and secret_key", context, v.Name)
}
case EnvVarTypeConfigMapRef:
if v.ConfigMapName == "" || v.ConfigMapKey == "" {
return fmt.Errorf("%s: env var %q (configmap_ref) requires configmap_name and configmap_key", context, v.Name)
}
default:
return fmt.Errorf("%s: env var %q has unknown type %q (valid: plain, secret_ref, configmap_ref)", context, v.Name, v.Type)
}
return nil
}
// EnvironmentKeys returns the sorted list of environment names.
func EnvironmentKeys(cfg *KforgeConfig) []string {
keys := make([]string, 0, len(cfg.Environments))
for k := range cfg.Environments {
keys = append(keys, k)
}
return keys
}
+274
View File
@@ -0,0 +1,274 @@
package config
// ------------------------------------------------------------
// Root config — mirrors kforge.yml top-level structure
// ------------------------------------------------------------
// KforgeConfig is the top-level struct unmarshalled from kforge.yml.
type KforgeConfig struct {
Meta MetaConfig `yaml:"meta"`
Registry RegistryConfig `yaml:"registry"`
DNS DNSConfig `yaml:"dns"`
Cluster ClusterConfig `yaml:"cluster"`
Defaults DefaultsConfig `yaml:"defaults"`
Infrastructure InfrastructureConfig `yaml:"infrastructure"`
Environments map[string]EnvironmentConfig `yaml:"environments"`
}
// ------------------------------------------------------------
// Meta
// ------------------------------------------------------------
type MetaConfig struct {
Name string `yaml:"name"`
Tenant string `yaml:"tenant"`
NameOverride *string `yaml:"name_override,omitempty"`
PreviousName *string `yaml:"previous_name,omitempty"`
}
// ------------------------------------------------------------
// Registry
// ------------------------------------------------------------
type RegistryConfig struct {
URL string `yaml:"url"`
Repository string `yaml:"repository,omitempty"` // default: ${tenant}/${name}
PullSecret string `yaml:"pull_secret,omitempty"` // default: regcred
}
// ------------------------------------------------------------
// DNS
// ------------------------------------------------------------
type DNSConfig struct {
Provider string `yaml:"provider"` // "cloudflare"
Cloudflare CloudflareConfig `yaml:"cloudflare"`
NodeIP string `yaml:"node_ip,omitempty"` // default: ${KFORGE_NODE_IP}
SkipDNS bool `yaml:"skip_dns,omitempty"`
}
type CloudflareConfig struct {
APIToken string `yaml:"api_token"`
Zones []ZoneEntry `yaml:"zones"`
Proxied bool `yaml:"proxied,omitempty"`
}
type ZoneEntry struct {
Name string `yaml:"name"`
ZoneID string `yaml:"zone_id"`
}
// ------------------------------------------------------------
// Cluster
// ------------------------------------------------------------
type ClusterConfig struct {
TLSIssuer string `yaml:"tls_issuer,omitempty"` // default: letsencrypt-prod
IngressClass string `yaml:"ingress_class,omitempty"` // default: nginx
CNPG CNPGConfig `yaml:"cnpg"`
NamespacePattern string `yaml:"namespace_pattern,omitempty"` // default: "${env}"
}
type CNPGConfig struct {
Host string `yaml:"host,omitempty"` // default: cnpg-main-rw.default.svc.cluster.local
HostOverride *string `yaml:"host_override,omitempty"`
}
// ------------------------------------------------------------
// Defaults
// ------------------------------------------------------------
type DefaultsConfig struct {
ImagePullPolicy string `yaml:"image_pull_policy,omitempty"` // default: Always
Replicas *int `yaml:"replicas,omitempty"` // default: 1
Port *int `yaml:"port,omitempty"` // default: 3000
ServiceType string `yaml:"service_type,omitempty"` // default: ClusterIP
Dockerfile string `yaml:"dockerfile,omitempty"` // default: Dockerfile
HealthCheck HealthCheckConfig `yaml:"health_check"`
Resources ResourceConfig `yaml:"resources"`
EnvVars []EnvVarConfig `yaml:"env_vars,omitempty"`
}
type HealthCheckConfig struct {
Path string `yaml:"path,omitempty"` // default: /healthcheck
Port *int `yaml:"port,omitempty"` // default: defaults.port
InitialDelaySeconds int `yaml:"initial_delay_seconds,omitempty"` // default: 15
PeriodSeconds int `yaml:"period_seconds,omitempty"` // default: 10
TimeoutSeconds int `yaml:"timeout_seconds,omitempty"` // default: 5
FailureThreshold int `yaml:"failure_threshold,omitempty"` // default: 3
Liveness *bool `yaml:"liveness,omitempty"` // default: true
Readiness *bool `yaml:"readiness,omitempty"` // default: true
}
type ResourceConfig struct {
Requests ResourceRequirements `yaml:"requests"`
Limits ResourceRequirements `yaml:"limits"`
}
type ResourceRequirements struct {
CPU string `yaml:"cpu,omitempty"`
Memory string `yaml:"memory,omitempty"`
}
// ------------------------------------------------------------
// Env vars — three source types
// ------------------------------------------------------------
type EnvVarType string
const (
EnvVarTypePlain EnvVarType = "plain"
EnvVarTypeSecretRef EnvVarType = "secret_ref"
EnvVarTypeConfigMapRef EnvVarType = "configmap_ref"
)
type EnvVarConfig struct {
Name string `yaml:"name"`
Type EnvVarType `yaml:"type"` // plain | secret_ref | configmap_ref
Value string `yaml:"value,omitempty"` // plain only
// secret_ref
SecretName string `yaml:"secret_name,omitempty"`
SecretKey string `yaml:"secret_key,omitempty"`
// configmap_ref
ConfigMapName string `yaml:"configmap_name,omitempty"`
ConfigMapKey string `yaml:"configmap_key,omitempty"`
}
// ------------------------------------------------------------
// Infrastructure — root defaults + per-env overrides
// ------------------------------------------------------------
// InfrastructureConfig holds the full infra config for either
// the root defaults block or a per-environment override block.
// Pointers are used so we can distinguish "not set" from "false".
type InfrastructureConfig struct {
Database *DatabaseInfraConfig `yaml:"database,omitempty"`
Cache *CacheInfraConfig `yaml:"cache,omitempty"`
Storage *StorageInfraConfig `yaml:"storage,omitempty"`
Queue *QueueInfraConfig `yaml:"queue,omitempty"`
Search *SearchInfraConfig `yaml:"search,omitempty"`
Monitoring *MonitoringInfraConfig `yaml:"monitoring,omitempty"`
}
// InfraBase holds the common enabled flag present on every
// infrastructure service. Embedded in each service config.
type InfraBase struct {
// Enabled defaults to true if the block exists in the root
// infrastructure section, and inherits that value in envs.
// Set explicitly to false to disable for a specific env.
Enabled *bool `yaml:"enabled,omitempty"`
}
type DatabaseInfraConfig struct {
InfraBase `yaml:",inline"`
Provider string `yaml:"provider,omitempty"` // cnpg
DatabaseName string `yaml:"database_name,omitempty"` // default: ${full_name} slugified
}
type CacheInfraConfig struct {
InfraBase `yaml:",inline"`
Provider string `yaml:"provider,omitempty"` // valkey | redis
Mode string `yaml:"mode,omitempty"` // standalone | cluster
Replicas *int `yaml:"replicas,omitempty"` // cluster mode only
}
type StorageInfraConfig struct {
InfraBase `yaml:",inline"`
Provider string `yaml:"provider,omitempty"` // minio
Mode string `yaml:"mode,omitempty"` // standalone | distributed
}
type QueueInfraConfig struct {
InfraBase `yaml:",inline"`
Provider string `yaml:"provider,omitempty"` // nats | rabbitmq
}
type SearchInfraConfig struct {
InfraBase `yaml:",inline"`
Provider string `yaml:"provider,omitempty"` // meilisearch
}
type MonitoringInfraConfig struct {
InfraBase `yaml:",inline"`
Provider string `yaml:"provider,omitempty"` // prometheus
MetricsPath string `yaml:"metrics_path,omitempty"` // default: /metrics
MetricsPort *int `yaml:"metrics_port,omitempty"` // default: defaults.port
}
// ------------------------------------------------------------
// Environment
// ------------------------------------------------------------
type EnvironmentConfig struct {
// EnvPrefix overrides the short prefix used in ${env_prefix}.
// Defaults to first 4 chars of the environment key if omitted.
EnvPrefix *string `yaml:"env_prefix,omitempty"`
Namespace string `yaml:"namespace,omitempty"` // overrides cluster.namespace_pattern
Replicas *int `yaml:"replicas,omitempty"`
ImageTag string `yaml:"image_tag,omitempty"` // default: latest
// Registry override: overrides the root registry for this env.
Registry *RegistryConfig `yaml:"registry,omitempty"`
EnvVars []EnvVarConfig `yaml:"env_vars,omitempty"`
Ingress IngressConfig `yaml:"ingress"`
Infrastructure InfrastructureConfig `yaml:"infrastructure"`
CronJobs []CronJobConfig `yaml:"cron_jobs,omitempty"`
Lifecycle LifecycleConfig `yaml:"lifecycle"`
}
// ------------------------------------------------------------
// Ingress
// ------------------------------------------------------------
type IngressConfig struct {
Hosts []IngressHost `yaml:"hosts"`
Auth IngressAuth `yaml:"auth"`
}
type IngressHost struct {
Hostname string `yaml:"hostname"`
TLS bool `yaml:"tls"`
DNSRecord bool `yaml:"dns_record,omitempty"`
}
type IngressAuth struct {
Enabled bool `yaml:"enabled,omitempty"`
Users []string `yaml:"users,omitempty"`
SecretName string `yaml:"secret_name,omitempty"` // default: ${full_name}-basic-auth
}
// ------------------------------------------------------------
// CronJob
// ------------------------------------------------------------
type CronJobConfig struct {
Name string `yaml:"name"`
Schedule string `yaml:"schedule"`
Command []string `yaml:"command"`
ImageOverride *string `yaml:"image_override,omitempty"`
InheritEnv *bool `yaml:"inherit_env,omitempty"` // default: true
EnvVars []EnvVarConfig `yaml:"env_vars,omitempty"`
Resources *ResourceConfig `yaml:"resources,omitempty"` // inherits defaults if nil
// Kubernetes CronJob tuning
RestartPolicy string `yaml:"restart_policy,omitempty"` // default: OnFailure
ConcurrencyPolicy string `yaml:"concurrency_policy,omitempty"` // default: Forbid
SuccessfulJobsHistoryLimit *int `yaml:"successful_jobs_history,omitempty"` // default: 3
FailedJobsHistoryLimit *int `yaml:"failed_jobs_history,omitempty"` // default: 1
}
// ------------------------------------------------------------
// Lifecycle
// ------------------------------------------------------------
type LifecycleConfig struct {
// If false (default), kforge renames resources when meta.name changes.
// If true, kforge deletes old resources after delete_grace_seconds.
Delete bool `yaml:"delete,omitempty"`
DeleteGraceSeconds int `yaml:"delete_grace_seconds,omitempty"` // default: 300
}