package config import ( "errors" "fmt" "os" "strings" monov1alpha1 "undecided.project/monok8s/pkg/apis/monok8s/v1alpha1" "gopkg.in/yaml.v3" ) const EnvVar = "MONOKSCONFIG" type Loader struct{} func (Loader) ResolvePath(flagValue string) (string, error) { if strings.TrimSpace(flagValue) != "" { return flagValue, nil } if env := strings.TrimSpace(os.Getenv(EnvVar)); env != "" { return env, nil } return "", fmt.Errorf("config path not provided; pass -c or set %s", EnvVar) } func (Loader) Load(path string) (*monov1alpha1.MonoKSConfig, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } var cfg monov1alpha1.MonoKSConfig if err := yaml.Unmarshal(data, &cfg); err != nil { return nil, err } if cfg.Kind == "" { cfg.Kind = "MonoKSConfig" } if cfg.APIVersion == "" { cfg.APIVersion = monov1alpha1.Group + "/" + monov1alpha1.Version } ApplyDefaults(&cfg) if err := Validate(&cfg); err != nil { return nil, err } return &cfg, nil } func ApplyDefaults(cfg *monov1alpha1.MonoKSConfig) { if cfg.Spec.PodSubnet == "" { cfg.Spec.PodSubnet = "10.244.0.0/16" } if cfg.Spec.ServiceSubnet == "" { cfg.Spec.ServiceSubnet = "10.96.0.0/12" } if cfg.Spec.ClusterName == "" { cfg.Spec.ClusterName = "monok8s" } if cfg.Spec.ClusterDomain == "" { cfg.Spec.ClusterDomain = "cluster.local" } if cfg.Spec.ContainerRuntimeEndpoint == "" { cfg.Spec.ContainerRuntimeEndpoint = "unix:///var/run/crio/crio.sock" } if cfg.Spec.BootstrapMode == "" { cfg.Spec.BootstrapMode = "init" } if cfg.Spec.JoinKind == "" { cfg.Spec.JoinKind = "worker" } if cfg.Spec.CNIPlugin == "" { cfg.Spec.CNIPlugin = "none" } if len(cfg.Spec.KubeProxyNodePortAddresses) == 0 { cfg.Spec.KubeProxyNodePortAddresses = []string{"primary"} } } func Validate(cfg *monov1alpha1.MonoKSConfig) error { var problems []string if cfg.Kind != "MonoKSConfig" { problems = append(problems, "kind must be MonoKSConfig") } if cfg.APIVersion != monov1alpha1.Group+"/"+monov1alpha1.Version { problems = append(problems, "apiVersion must be "+monov1alpha1.Group+"/"+monov1alpha1.Version) } if strings.TrimSpace(cfg.Spec.KubernetesVersion) == "" { problems = append(problems, "spec.kubernetesVersion is required") } if strings.TrimSpace(cfg.Spec.NodeName) == "" { problems = append(problems, "spec.nodeName is required") } if strings.TrimSpace(cfg.Spec.APIServerAdvertiseAddress) == "" { problems = append(problems, "spec.apiServerAdvertiseAddress is required") } if strings.TrimSpace(cfg.Spec.Network.Hostname) == "" { problems = append(problems, "spec.network.hostname is required") } if strings.TrimSpace(cfg.Spec.Network.ManagementIface) == "" { problems = append(problems, "spec.network.managementIface is required") } if !strings.Contains(cfg.Spec.Network.ManagementCIDR, "/") { problems = append(problems, "spec.network.managementCIDR must include a CIDR prefix") } if cfg.Spec.BootstrapMode != "init" && cfg.Spec.BootstrapMode != "join" { problems = append(problems, "spec.bootstrapMode must be init or join") } if cfg.Spec.JoinKind != "worker" && cfg.Spec.JoinKind != "control-plane" { problems = append(problems, "spec.joinKind must be worker or control-plane") } for _, ns := range cfg.Spec.Network.DNSNameservers { if ns == "10.96.0.10" { problems = append(problems, "spec.network.dnsNameservers must not include cluster DNS service IP 10.96.0.10") } } if cfg.Spec.BootstrapMode == "join" { if cfg.Spec.APIServerEndpoint == "" { problems = append(problems, "spec.apiServerEndpoint is required for join mode") } if cfg.Spec.BootstrapToken == "" { problems = append(problems, "spec.bootstrapToken is required for join mode") } if cfg.Spec.DiscoveryTokenCACertHash == "" { problems = append(problems, "spec.discoveryTokenCACertHash is required for join mode") } if cfg.Spec.JoinKind == "control-plane" && cfg.Spec.ControlPlaneCertKey == "" { problems = append(problems, "spec.controlPlaneCertKey is required for control-plane join") } } if len(problems) > 0 { return errors.New(strings.Join(problems, "; ")) } return nil }