diff --git a/docs/docs/guides.md b/docs/docs/guides.md index cdee0bff..2db034c5 100644 --- a/docs/docs/guides.md +++ b/docs/docs/guides.md @@ -40,6 +40,45 @@ Here's a more detailed example [talconfig.yaml](https://github.com/budimanjojo/t To see all the available options of the configuration file, head over to [Configuration Reference](reference/configuration.md). +## DRY (Don't Repeat Yourself) in `talconfig.yaml` + +A lot of times, you have similar configurations for all your nodes. +Instead of writing them multiple times for each node, you can make use of `controlPlane` and `worker` fields as "global configurations" for all your node group. + +```{.yaml hl_lines="12-22"} +--- +clusterName: my-cluster +nodes: + - hostname: cp1 + controlPlane: true + ipAddress: 192.168.200.11 + installDisk: /dev/sda + - hostname: cp2 + controlPlane: true + ipAddress: 192.168.200.12 + installDisk: /dev/sda +controlPlane: + schematic: + customization: + extraKernelArgs: + - net.ifnames=0 + patches: + - |- + - op: add + path: /machine/kubelet/extraArgs + value: + rotate-server-certificates: "true" +``` + +The `schematic` and `patches` defined in `controlPlane` will be applied to both `cp1` and `cp2` because they're both in the group of `controlPlane` nodes. + +!!! note + + [NodeConfigs](./reference/configuration.md#nodeconfigs) you define in `controlPlane` or `worker` will be overwritten if you define them per node in `nodes[]` section. + But, for `patches` and `extraManifests` they are appended instead because it makes more sense. + + You **can** modify the default behavior by adding `overridePatches: true` and `overrideExtraManifests: true` inside `nodes[]` for node you don't want the default behavior. + ## Adding Talos extensions and kernel arguments Talos v1.5 introduced a new unified way to generate boot assets for installer container image that you can build yourself using their `imager` container or use [image-factory](https://factory.talos.dev/) to dynamically build it for you. diff --git a/docs/docs/reference/configuration.md b/docs/docs/reference/configuration.md index 8360ee29..04fe9878 100644 --- a/docs/docs/reference/configuration.md +++ b/docs/docs/reference/configuration.md @@ -57,18 +57,6 @@ nodes: :white_check_mark: - -`talosImageURL` -string -**DEPRECATED, won't do anything, use `nodes[].talosImageURL` instead**.
*Show example* -```yaml -talosImageURL: ghcr.io/siderolabs/installer -``` -
-`"ghcr.io/siderolabs/installer"` -:negative_squared_cross_mark: - - `talosVersion` string @@ -215,10 +203,14 @@ patches: `controlPlane` -[ControlPlane](#controlplane) -Configurations targetted for controlplane nodes.
*Show example* +[NodeConfigs](#nodeconfigs) +Configurations targetted for all controlplane nodes.
*Show example* ```yaml controlPlane: + kernelModules: + - name: br_netfilter + parameters: + - nf_conntrack_max=131072 patches: - |- - op: add @@ -238,10 +230,14 @@ controlPlane: `worker` -[Worker](#worker) -Configurations targetted for worker nodes.
*Show example* +[NodeConfigs](#nodeconfigs) +Configurations targetted for all worker nodes.
*Show example* ```yaml worker: + kernelModules: + - name: br_netfilter + parameters: + - nf_conntrack_max=131072 patches: - |- - op: add @@ -306,18 +302,6 @@ installDisk: /dev/sda :white_check_mark: - -`talosImageURL` -string -Allows for supplying the node level image used to perform the installation.
*Show example* -```yaml -talosImageURL: factory.talos.dev/installer/e9c7ef96884d4fbc8c0a1304ccca4bb0287d766a8b4125997cb9dbe84262144e -``` -
-`""` -:negative_squared_cross_mark: - - `installDiskSelector` [InstallDiskSelector](#installdiskselector) @@ -334,6 +318,82 @@ installDiskSelector: :negative_squared_cross_mark: + +`controlPlane` +bool +Whether the node is a controlplane.
*Show example* +```yaml +controlPlane: true +``` + +`false` +:negative_squared_cross_mark: + + + +`overridePatches` +bool +
Whether `patches` defined here should override the one defined in node group.By default they will get appended instead.
*Show example* +```yaml +overridePatches: true +``` + +`false` +:negative_squared_cross_mark: + + + +`overrideExtraManifests` +bool +
Whether `extraManifests` defined here should override the one defined in node group.By default they will get appended instead.
*Show example* +```yaml +overrideExtraManifests: true +``` + +`false` +:negative_squared_cross_mark: + + + +- +[NodeConfigs](#nodeconfigs) +Node specific configurations that will override node group configurations.
*Show example* +```yaml +talosImageURL: factory.talos.dev/installer/e9c7ef96884d4fbc8c0a1304ccca4bb0287d766a8b4125997cb9dbe84262144e +nodeLabels: + rack: rack1a +nodeTaints: + exampleTaint: exampletaintValue:NoSchedule +disableSearchDomain: true +``` + +`nil` +:negative_squared_cross_mark: + + + + +## NodeConfigs + +`NodeConfigs` defines machine configurations. + + + + + + + + + + + + + + @@ -370,18 +430,6 @@ ingressFirewall: - - - - - - - - @@ -450,19 +498,6 @@ machineFiles: - - - - - - - - @@ -566,39 +601,6 @@ patches: - - - - - - - - - - - - - - - -
FieldTypeDescriptionDefault ValueRequired
`talosImageURL`stringAllows for supplying the node level image used to perform the installation.
*Show example* +```yaml +talosImageURL: factory.talos.dev/installer/e9c7ef96884d4fbc8c0a1304ccca4bb0287d766a8b4125997cb9dbe84262144e +``` +
`""`:negative_squared_cross_mark:
`machineSpec` [MachineSpec](#machinespec):negative_squared_cross_mark:
`controlPlane`boolWhether the node is a controlplane.
*Show example* -```yaml -controlPlane: true -``` -
`false`:negative_squared_cross_mark:
`nodeLabels` map[string]string:negative_squared_cross_mark:
`extensions`[][InstallExtensionConfig](#installextensionconfig)
**DEPRECATED, use `schematic` instead**.List of additional system extensions image to install.
*Show example* -```yaml -extensions: - - image: ghcr.io/siderolabs/tailscale:1.44.0 -``` -
`[]`:negative_squared_cross_mark:
`schematic` [Schematic](#schematic):negative_squared_cross_mark:
`configPatches`[]map[string]interface{}
**DEPRECATED, use `patches` instead**.List of RFC6902 JSON patches to be applied to the node.
*Show example* -```yaml -configPatches: - - op: add - path: /machine/install/extraKernelArgs - value: - - console=ttyS1 -``` -
`[]`:negative_squared_cross_mark:
`inlinePatch`map[string]interface{}
**DEPRECATED, use `patches` instead**.Strategic merge patches to be applied to the node.
*Show example* -```yaml -inlinePatch: - machine: - network: - interfaces: - - interface: eth0 - addresses: [192.168.200.11/24] -``` -
`map[]`:negative_squared_cross_mark:
## ImageFactory @@ -802,246 +804,6 @@ ingress: -## ControlPlane - -`ControlPlane` defines machine configurations for controlplane type nodes. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldTypeDescriptionDefault ValueRequired
`patches`[]string
Patches to be applied to all controlplane nodes.List of strings containing RFC6902 JSON patches, strategic merge patches,
or a file containing them.
*Show example* -```yaml -patches: - - |- - - op: add - path: /machine/kubelet/extraArgs - value: - rotate-server-certificates: "true" - - |- - machine: - env: - MYENV: value - - "@./a-patch.yaml" -``` -
`[]`:negative_squared_cross_mark:
`configPatches`[]map[string]interface{}
**DEPRECATED, use `patches` instead**.List of RFC6902 JSON patches to be applied to all controlplane nodes.
*Show example* -```yaml -configPatches: - - op: add - path: /machine/install/extraKernelArgs - value: - - console=ttyS1 -``` -
`[]`:negative_squared_cross_mark:
`inlinePatch`map[string]interface{}
**DEPRECATED, use `patches` instead**.Strategic merge patches to be applied to all controlplane nodes.
*Show example* -```yaml -inlinePatch: - machine: - network: - interfaces: - - interface: eth0 - addresses: [192.168.200.11/24] -``` -
`map[]`:negative_squared_cross_mark:
`schematic`[Schematic](#schematic)Configure Talos image customization to be applied to all controlplane nodes
*Show example* -```yaml -schematic: - customization: - extraKernelArgs: - - net.ifnames=0 - systemExtensions: - officialExtensions: - - siderolabs/intel-ucode -``` -
`nil`:negative_squared_cross_mark:
`ingressFirewall`[IngressFirewall](#ingressfirewall)Firewall specification for all controlplane nodes.
*Show example* -```yaml -ingressFirewall: - defaultAction: block - rules: - - name: kubelet-ingress - portSelector: - ports: - - 10250 - protocol: tcp - ingress: - - subnet: 172.20.0.0/24 - except: 172.20.0.1/32 -``` -
`nil`:negative_squared_cross_mark:
`extraManifests`[]stringList of manifest files to be added to all controlplane nodes.
*Show example* -```yaml -extraManifests: - - etcd-firewall.yaml - - kubelet-firewall.yaml -``` -
`[]`:negative_squared_cross_mark:
- -## Worker - -`Worker` defines machine configurations for worker type nodes. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldTypeDescriptionDefault ValueRequired
`patches`[]string
Patches to be applied to all worker nodes.List of strings containing RFC6902 JSON patches, strategic merge patches,
or a file containing them.
*Show example* -```yaml -patches: - - |- - - op: add - path: /machine/kubelet/extraArgs - value: - rotate-server-certificates: "true" - - |- - machine: - env: - MYENV: value - - "@./a-patch.yaml" -``` -
`[]`:negative_squared_cross_mark:
`configPatches`[]map[string]interface{}
**DEPRECATED, use `patches` instead**.List of RFC6902 JSON patches to be applied to all worker nodes.
*Show example* -```yaml -configPatches: - - op: add - path: /machine/install/extraKernelArgs - value: - - console=ttyS1 -``` -
`[]`:negative_squared_cross_mark:
`inlinePatch`map[string]interface{}
**DEPRECATED, use `patches` instead**.Strategic merge patches to be applied to all worker nodes.
*Show example* -```yaml -inlinePatch: - machine: - network: - interfaces: - - interface: eth0 - addresses: [192.168.200.11/24] -``` -
`map[]`:negative_squared_cross_mark:
`schematic`[Schematic](#schematic)Configure Talos image customization to be applied to all worker nodes
*Show example* -```yaml -schematic: - customization: - extraKernelArgs: - - net.ifnames=0 - systemExtensions: - officialExtensions: - - siderolabs/intel-ucode -``` -
`nil`:negative_squared_cross_mark:
`ingressFirewall`[IngressFirewall](#ingressfirewall)Firewall specification for all worker nodes.
*Show example* -```yaml -ingressFirewall: - defaultAction: block - rules: - - name: kubelet-ingress - portSelector: - ports: - - 10250 - protocol: tcp - ingress: - - subnet: 172.20.0.0/24 - except: 172.20.0.1/32 -``` -
`nil`:negative_squared_cross_mark:
`extraManifests`[]stringList of manifest files to be added to all worker nodes.
*Show example* -```yaml -extraManifests: - - etcd-firewall.yaml - - kubelet-firewall.yaml -``` -
`[]`:negative_squared_cross_mark:
- ## CNIConfig `CNIConfig` is type of upstream Talos `v1alpha1.CNIConfig` diff --git a/pkg/config/config.go b/pkg/config/config.go index ac77d048..2056a4e9 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -9,7 +9,6 @@ import ( type TalhelperConfig struct { ClusterName string `yaml:"clusterName" jsonschema:"required,description=Name of the cluster"` - TalosImageURL string `yaml:"talosImageURL" jsonschema:"default=ghcr.io/siderolabs/installer,description=DEPRECATED: will not do anything, use \"nodes[].talosImageURL\" instead"` TalosVersion string `yaml:"talosVersion,omitempty" jsonschema:"example=v1.5.4,description=Talos version to perform installation"` KubernetesVersion string `yaml:"kubernetesVersion,omitempty" jsonschema:"example=v1.27.0,description=Kubernetes version to use"` Endpoint string `yaml:"endpoint" jsonschema:"required,example=https://192.168.200.10:6443,description=Cluster's controlplane endpoint"` @@ -24,51 +23,36 @@ type TalhelperConfig struct { Patches []string `yaml:"patches,omitempty" jsonschema:"description=Patches to be applied to all nodes"` Nodes []Node `yaml:"nodes" jsonschema:"required,description=List of configurations for Node"` ImageFactory ImageFactory `yaml:"imageFactory,omitempty" jsonschema:"Configuration for image factory"` - ControlPlane controlPlane `yaml:"controlPlane,omitempty" jsonschema:"description=Configurations targetted for controlplane nodes"` - Worker worker `yaml:"worker,omitempty" jsonschema:"description=Configurations targetted for worker nodes"` + ControlPlane NodeConfigs `yaml:"controlPlane,omitempty" jsonschema:"description=Configurations targetted for all controlplane nodes"` + Worker NodeConfigs `yaml:"worker,omitempty" jsonschema:"description=Configurations targetted for all worker nodes"` } type Node struct { - Hostname string `yaml:"hostname" jsonschema:"required,description=Hostname of the node"` - IPAddress string `yaml:"ipAddress,omitempty" jsonschema:"required,example=192.168.200.11,description=IP address where the node can be reached"` - ControlPlane bool `yaml:"controlPlane" jsonschema:"description=Whether the node is a controlplane"` - NodeLabels map[string]string `yaml:"nodeLabels" jsonschema:"description=Labels to be added to the node"` - NodeTaints map[string]string `yaml:"nodeTaints" jsonschema:"description=Node taints for the node. Effect is optional"` - InstallDisk string `yaml:"installDisk,omitempty" jsonschema:"oneof_required=installDiskSelector,description=The disk used for installation"` - InstallDiskSelector *v1alpha1.InstallDiskSelector `yaml:"installDiskSelector,omitempty" jsonschema:"oneof_required=installDisk,description=Look up disk used for installation"` - MachineDisks []*v1alpha1.MachineDisk `yaml:"machineDisks,omitempty" jsonschema:"description=List of additional disks to partition, format, mount"` - MachineFiles []*v1alpha1.MachineFile `yaml:"machineFiles,omitempty" jsonschema:"description=List of files to create inside the node"` - Extensions []v1alpha1.InstallExtensionConfig `yaml:"extensions,omitempty" jsonschema:"description=DEPRECATED: use \"schematic\" instead"` - DisableSearchDomain bool `yaml:"disableSearchDomain,omitempty" jsonschema:"description=Whether to disable generating default search domain"` - KernelModules []*v1alpha1.KernelModuleConfig `yaml:"kernelModules,omitempty" jsonschema:"description=List of additional kernel modules to load inside the node"` - Nameservers []string `yaml:"nameservers,omitempty" jsonschema:"description=List of nameservers for the node"` - NetworkInterfaces []*v1alpha1.Device `yaml:"networkInterfaces,omitempty" jsonschema:"description=List of network interface configuration for the node"` - ExtraManifests []string `yaml:"extraManifests,omitempty" jsonschema:"description=List of manifest files to be added to the node"` - ConfigPatches []map[string]interface{} `yaml:"configPatches,omitempty" jsonschema:"description=DEPRECATED: use \"patches\" instead"` - InlinePatch map[string]interface{} `yaml:"inlinePatch,omitempty" jsonschema:"description=DEPRECATED: use \"patches\" instead"` - Patches []string `yaml:"patches,omitempty" jsonschema:"description=Patches to be applied to the node"` - TalosImageURL string `yaml:"talosImageURL" jsonschema:"example=factory.talos.dev/installer/e9c7ef96884d4fbc8c0a1304ccca4bb0287d766a8b4125997cb9dbe84262144e,description=Talos installer image url for the node"` - Schematic *schematic.Schematic `yaml:"schematic,omitempty" jsonschema:"description=Talos image customization to be used in the installer image"` - MachineSpec MachineSpec `yaml:"machineSpec,omitempty" jsonschema:"description=Machine hardware specification"` - IngressFirewall *IngressFirewall `yaml:"ingressFirewall,omitempty" jsonschema:"description=Machine firewall specification"` + Hostname string `yaml:"hostname" jsonschema:"required,description=Hostname of the node"` + IPAddress string `yaml:"ipAddress,omitempty" jsonschema:"required,example=192.168.200.11,description=IP address where the node can be reached"` + ControlPlane bool `yaml:"controlPlane" jsonschema:"description=Whether the node is a controlplane"` + InstallDisk string `yaml:"installDisk,omitempty" jsonschema:"oneof_required=installDiskSelector,description=The disk used for installation"` + InstallDiskSelector *v1alpha1.InstallDiskSelector `yaml:"installDiskSelector,omitempty" jsonschema:"oneof_required=installDisk,description=Look up disk used for installation"` + OverridePatches bool `yaml:"overridePatches,omitempty" jsonschema:"description=Whether \"patches\" defined here should override the one defined in node group"` + OverrideExtraManifests bool `yaml:"overrideExtraManifests,omitempty" jsonschema:"description=Whether \"extraManifests\" defined here should override the one defined in node group"` + NodeConfigs `yaml:",inline" jsonschema:"description=Node specific configurations that will override node group configurations"` } -type controlPlane struct { - ConfigPatches []map[string]interface{} `yaml:"configPatches,omitempty" jsonschema:"description=DEPRECATED: use \"patches\" instead"` - InlinePatch map[string]interface{} `yaml:"inlinePatch,omitempty" jsonschema:"description=DEPRECATED: use \"patches\" instead"` - Patches []string `yaml:"patches,omitempty" jsonschema:"description=Patches to be applied to all controlplane nodes"` - Schematic *schematic.Schematic `yaml:"schematic,omitempty" jsonschema:"description=Talos image customization to be applied to all controlplane nodes"` - IngressFirewall *IngressFirewall `yaml:"ingressFirewall,omitempty" jsonschema:"description=Firewall specification for all controlplane nodes"` - ExtraManifests []string `yaml:"extraManifests,omitempty" jsonschema:"description=List of manifest files to be added to all controlplane nodes"` -} - -type worker struct { - ConfigPatches []map[string]interface{} `yaml:"configPatches,omitempty" jsonschema:"description=DEPRECATED: use \"patches\" instead"` - InlinePatch map[string]interface{} `yaml:"inlinePatch,omitempty" jsonschema:"description=DEPRECATED: use \"patches\" instead"` - Patches []string `yaml:"patches,omitempty" jsonschema:"description=Patches to be applied to all worker nodes"` - Schematic *schematic.Schematic `yaml:"schematic,omitempty" jsonschema:"description=Talos image customization to be applied to all worker nodes"` - IngressFirewall *IngressFirewall `yaml:"ingressFirewall,omitempty" jsonschema:"description=Firewall specification for all worker nodes"` - ExtraManifests []string `yaml:"extraManifests,omitempty" jsonschema:"description=List of manifest files to be added to all worker nodes"` +type NodeConfigs struct { + NodeLabels map[string]string `yaml:"nodeLabels" jsonschema:"description=Labels to be added to the node"` + NodeTaints map[string]string `yaml:"nodeTaints" jsonschema:"description=Node taints for the node. Effect is optional"` + MachineDisks []*v1alpha1.MachineDisk `yaml:"machineDisks,omitempty" jsonschema:"description=List of additional disks to partition, format, mount"` + MachineFiles []*v1alpha1.MachineFile `yaml:"machineFiles,omitempty" jsonschema:"description=List of files to create inside the node"` + DisableSearchDomain bool `yaml:"disableSearchDomain,omitempty" jsonschema:"description=Whether to disable generating default search domain"` + KernelModules []*v1alpha1.KernelModuleConfig `yaml:"kernelModules,omitempty" jsonschema:"description=List of additional kernel modules to load inside the node"` + Nameservers []string `yaml:"nameservers,omitempty" jsonschema:"description=List of nameservers for the node"` + NetworkInterfaces []*v1alpha1.Device `yaml:"networkInterfaces,omitempty" jsonschema:"description=List of network interface configuration for the node"` + ExtraManifests []string `yaml:"extraManifests,omitempty" jsonschema:"description=List of manifest files to be added to the node"` + Patches []string `yaml:"patches,omitempty" jsonschema:"description=Patches to be applied to the node"` + TalosImageURL string `yaml:"talosImageURL" jsonschema:"example=factory.talos.dev/installer/e9c7ef96884d4fbc8c0a1304ccca4bb0287d766a8b4125997cb9dbe84262144e,description=Talos installer image url for the node"` + Schematic *schematic.Schematic `yaml:"schematic,omitempty" jsonschema:"description=Talos image customization to be used in the installer image"` + MachineSpec MachineSpec `yaml:"machineSpec,omitempty" jsonschema:"description=Machine hardware specification"` + IngressFirewall *IngressFirewall `yaml:"ingressFirewall,omitempty" jsonschema:"description=Machine firewall specification"` } type ImageFactory struct { diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 9806fc15..aa9972e2 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -71,15 +71,6 @@ func (c *TalhelperConfig) GetClusterSvcNets() []string { return c.ClusterSvcNets } -// GetInstallerURL returns installer URL string. -func (c *TalhelperConfig) GetInstallerURL() string { - if c.TalosImageURL != "" { - return c.TalosImageURL + ":" + c.GetTalosVersion() - } - - return "ghcr.io/siderolabs/installer:" + c.GetTalosVersion() -} - // GetImageFactory returns default `imageFactory` if not specified. func (c *TalhelperConfig) GetImageFactory() *ImageFactory { result := &ImageFactory{ diff --git a/pkg/config/loader.go b/pkg/config/loader.go index aff1fe42..d84b60dd 100644 --- a/pkg/config/loader.go +++ b/pkg/config/loader.go @@ -35,13 +35,9 @@ func LoadAndValidateFromFile(filePath string, envPaths []string) (*TalhelperConf for k, node := range cfg.Nodes { switch node.ControlPlane { case true: - if cfg.ControlPlane.Schematic != nil && node.Schematic == nil { - cfg.Nodes[k].Schematic = cfg.ControlPlane.Schematic - } + cfg.Nodes[k].OverrideGlobalCfg(cfg.ControlPlane) case false: - if cfg.Worker.Schematic != nil && node.Schematic == nil { - cfg.Nodes[k].Schematic = cfg.Worker.Schematic - } + cfg.Nodes[k].OverrideGlobalCfg(cfg.Worker) } } diff --git a/pkg/config/loader_test.go b/pkg/config/loader_test.go index 8f95fcfa..fdc3f870 100644 --- a/pkg/config/loader_test.go +++ b/pkg/config/loader_test.go @@ -19,12 +19,15 @@ func TestLoadAndValidateFromFile(t *testing.T) { IPAddress: "192.168.200.10", ControlPlane: true, InstallDisk: "/dev/sda", - Schematic: &schematic.Schematic{ - Customization: schematic.Customization{ - SystemExtensions: schematic.SystemExtensions{ - OfficialExtensions: []string{"siderolabs/tailscale"}, + NodeConfigs: NodeConfigs{ + Schematic: &schematic.Schematic{ + Customization: schematic.Customization{ + SystemExtensions: schematic.SystemExtensions{ + OfficialExtensions: []string{"siderolabs/tailscale"}, + }, }, }, + DisableSearchDomain: true, }, } expectedNode1 := Node{ @@ -32,9 +35,11 @@ func TestLoadAndValidateFromFile(t *testing.T) { IPAddress: "192.168.200.11", ControlPlane: false, InstallDisk: "/dev/sda", - Schematic: &schematic.Schematic{ - Customization: schematic.Customization{ - ExtraKernelArgs: []string{"net.ifnames=0"}, + NodeConfigs: NodeConfigs{ + Schematic: &schematic.Schematic{ + Customization: schematic.Customization{ + ExtraKernelArgs: []string{"net.ifnames=0"}, + }, }, }, } diff --git a/pkg/config/nodeconfigs.go b/pkg/config/nodeconfigs.go new file mode 100644 index 00000000..38048148 --- /dev/null +++ b/pkg/config/nodeconfigs.go @@ -0,0 +1,38 @@ +package config + +import ( + "reflect" +) + +func (node *Node) OverrideGlobalCfg(cfg NodeConfigs) *Node { + node.NodeConfigs = mergeNodeConfigs(node.NodeConfigs, cfg, node.OverridePatches, node.OverrideExtraManifests) + + return node +} + +func mergeNodeConfigs(patch, src NodeConfigs, overridePatches, overrideExtraManifest bool) NodeConfigs { + if len(src.Patches) > 0 && !overridePatches { + patch.Patches = append(patch.Patches, src.Patches...) + } + if len(src.ExtraManifests) > 0 && !overrideExtraManifest { + patch.ExtraManifests = append(patch.ExtraManifests, src.ExtraManifests...) + } + + patchValue := reflect.ValueOf(patch) + srcValue := reflect.ValueOf(src) + + result := reflect.New(patchValue.Type()).Elem() + + for i := 0; i < patchValue.NumField(); i++ { + patchField := patchValue.Field(i) + srcField := srcValue.Field(i) + + if !patchField.IsZero() { + result.Field(i).Set(patchField) + } else { + result.Field(i).Set(srcField) + } + } + + return result.Interface().(NodeConfigs) +} diff --git a/pkg/config/nodeconfigs_test.go b/pkg/config/nodeconfigs_test.go new file mode 100644 index 00000000..7ce425de --- /dev/null +++ b/pkg/config/nodeconfigs_test.go @@ -0,0 +1,79 @@ +package config + +import ( + "reflect" + "testing" + + "github.com/siderolabs/image-factory/pkg/schematic" + "github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1" +) + +func TestOverrideNodeConfigs(t *testing.T) { + globalCfg := NodeConfigs{ + NodeLabels: map[string]string{ + "testkey": "testValue", + }, + Schematic: &schematic.Schematic{ + Customization: schematic.Customization{ + ExtraKernelArgs: []string{"enable=1"}, + }, + }, + } + + node := Node{ + Hostname: "test-host", + IPAddress: "123.456.789.1", + InstallDisk: "/dev/test", + ControlPlane: true, + NodeConfigs: NodeConfigs{ + NodeLabels: map[string]string{ + "testkey": "overwritten", + }, + MachineDisks: []*v1alpha1.MachineDisk{ + { + DeviceName: "/dev/sda", + DiskPartitions: []*v1alpha1.DiskPartition{ + { + DiskSize: v1alpha1.DiskSize(1), + DiskMountPoint: "/hello", + }, + }, + }, + }, + }, + } + + expectedNode := Node{ + Hostname: "test-host", + IPAddress: "123.456.789.1", + InstallDisk: "/dev/test", + ControlPlane: true, + NodeConfigs: NodeConfigs{ + NodeLabels: map[string]string{ + "testkey": "overwritten", + }, + MachineDisks: []*v1alpha1.MachineDisk{ + { + DeviceName: "/dev/sda", + DiskPartitions: []*v1alpha1.DiskPartition{ + { + DiskSize: v1alpha1.DiskSize(1), + DiskMountPoint: "/hello", + }, + }, + }, + }, + Schematic: &schematic.Schematic{ + Customization: schematic.Customization{ + ExtraKernelArgs: []string{"enable=1"}, + }, + }, + }, + } + + node.OverrideGlobalCfg(globalCfg) + + if !reflect.DeepEqual(node, expectedNode) { + t.Errorf("got:\n%v\nwant:\n%v", node, expectedNode) + } +} diff --git a/pkg/config/testdata/talconfig.yaml b/pkg/config/testdata/talconfig.yaml index c3738e18..21f62f85 100644 --- a/pkg/config/testdata/talconfig.yaml +++ b/pkg/config/testdata/talconfig.yaml @@ -16,6 +16,7 @@ nodes: installDisk: /dev/sda controlPlane: false controlPlane: + disableSearchDomain: true schematic: customization: systemExtensions: diff --git a/pkg/config/validate.go b/pkg/config/validate.go index d6076120..7dc3f4c3 100644 --- a/pkg/config/validate.go +++ b/pkg/config/validate.go @@ -44,8 +44,6 @@ func (c TalhelperConfig) Validate() (Errors, Warnings) { checkDomain(c, &result) checkClusterNets(c, &result) checkCNIConfig(c, &result) - checkControlPlane(c, &result) - checkWorker(c, &result) for k, node := range c.Nodes { checkNodeRequiredCfg(node, k, &result) checkNodeIPAddress(node, k, &result) @@ -54,11 +52,9 @@ func (c TalhelperConfig) Validate() (Errors, Warnings) { checkNodeTaints(node, k, &result) checkNodeMachineDisks(node, k, &result) checkNodeMachineFiles(node, k, &result) - checkNodeExtensions(node, k, &result, &warns) checkNodeSchematic(node, k, c.GetTalosVersion(), &result) checkNodeNameServers(node, k, &result) checkNodeNetworkInterfaces(node, k, &result) - checkNodeConfigPatches(node, k, &result) checkNodeIngressFirewall(node, k, &result) checkNodeExtraManifests(node, k, &result) } diff --git a/pkg/config/validate_test.go b/pkg/config/validate_test.go index 1de9ad9d..3a5492d2 100644 --- a/pkg/config/validate_test.go +++ b/pkg/config/validate_test.go @@ -33,8 +33,6 @@ nodes: ipAddress: 1.2.3.4.5 installDisk: /dev/sda disableSearchDomain: true - extensions: - - image: hehe nameservers: - 8.8.8.8 networkInterfaces: @@ -44,9 +42,6 @@ nodes: routes: - network: 0.0.0.0/0 gateway: 1.2.3.4.5.6 - configPatches: - - op: del - path: /cluster ingressFirewall: defaultAction: block rules: @@ -73,41 +68,34 @@ nodes: - test.yaml `) - errs, warns, err := ValidateFromByte(data) + errs, _, err := ValidateFromByte(data) if err != nil { t.Fatal(err) } expectedErrors := map[string]bool{ - "clusterName": false, - "talosVersion": true, - "kubernetesVersion": true, - "endpoint": true, - "cniConfig": true, - "clusterPodNets": false, - "clusterSvcNets": true, - "controlPlane.ingressFirewall": true, - "worker.extraManifests": true, - "nodes[0].hostname": false, - "nodes[0].ipAddress": false, - "nodes[0].controlPlane": false, - "nodes[0].installDisk": false, - "nodes[0].nameservers": false, - "nodes[0].ingressFirewall": false, - "nodes[0].networkInterfaces": true, - "nodes[0].configPatches": true, - "nodes[1].hostname": true, - "nodes[1].ipAddress": true, - "nodes[1].installDisk": true, - "nodes[1].nodeLabels": true, - "nodes[1].nodeTaints": true, - "nodes[1].machineFiles": true, - "nodes[1].schematic": true, - "nodes[1].extraManifests": true, - } - - expectedWarnings := map[string]bool{ - "nodes[0].extensions": true, + "clusterName": false, + "talosVersion": true, + "kubernetesVersion": true, + "endpoint": true, + "cniConfig": true, + "clusterPodNets": false, + "clusterSvcNets": true, + "nodes[0].hostname": false, + "nodes[0].ipAddress": false, + "nodes[0].controlPlane": false, + "nodes[0].installDisk": false, + "nodes[0].nameservers": false, + "nodes[0].ingressFirewall": false, + "nodes[0].networkInterfaces": true, + "nodes[1].hostname": true, + "nodes[1].ipAddress": true, + "nodes[1].installDisk": true, + "nodes[1].nodeLabels": true, + "nodes[1].nodeTaints": true, + "nodes[1].machineFiles": true, + "nodes[1].schematic": true, + "nodes[1].extraManifests": true, } for k, v := range expectedErrors { @@ -115,10 +103,4 @@ nodes: t.Errorf("%s: got %t, want %t", k, errs.HasField(k), v) } } - - for k, v := range expectedWarnings { - if warns.HasField(k) != v { - t.Errorf("%s: got %t, want %t", k, warns.HasField(k), v) - } - } } diff --git a/pkg/config/validator.go b/pkg/config/validator.go index 05b236fa..3850657a 100644 --- a/pkg/config/validator.go +++ b/pkg/config/validator.go @@ -193,72 +193,6 @@ func checkCNIConfig(c TalhelperConfig, result *Errors) *Errors { return result } -func checkControlPlane(c TalhelperConfig, result *Errors) *Errors { - if len(c.ControlPlane.ConfigPatches) > 0 { - if !isRFC6902List(c.ControlPlane.ConfigPatches) { - result = result.Append(&Error{ - Kind: "InvalidControlPlaneConfigPatches", - Field: getFieldYamlTag(c, "ControlPlane.ConfigPatches"), - Message: formatError(multierror.Append(fmt.Errorf("doesn't look like list of RFC6902 JSON patches"))), - }) - } - } - - if c.ControlPlane.IngressFirewall != nil { - if err := checkIngressFirewall(c.ControlPlane.IngressFirewall); err != nil { - result = result.Append(&Error{ - Kind: "InvalidControlPlaneIngressFirewall", - Field: getFieldYamlTag(c, "ControlPlane.IngressFirewall"), - Message: formatError(multierror.Append(err)), - }) - } - } - - if len(c.ControlPlane.ExtraManifests) > 0 { - if err := checkExtraManifests(c.ControlPlane.ExtraManifests); err != nil { - result = result.Append(&Error{ - Kind: "InvalidControlPlaneExtraManifests", - Field: getFieldYamlTag(c, "ControlPlane.ExtraManifests"), - Message: formatError(multierror.Append(err)), - }) - } - } - return result -} - -func checkWorker(c TalhelperConfig, result *Errors) *Errors { - if len(c.Worker.ConfigPatches) > 0 { - if !isRFC6902List(c.Worker.ConfigPatches) { - result = result.Append(&Error{ - Kind: "InvalidWorkerConfigPatches", - Field: getFieldYamlTag(c, "Worker.ConfigPatches"), - Message: formatError(multierror.Append(fmt.Errorf("doesn't look like list of RFC6902 JSON patches"))), - }) - } - } - - if c.Worker.IngressFirewall != nil { - if err := checkIngressFirewall(c.Worker.IngressFirewall); err != nil { - result = result.Append(&Error{ - Kind: "InvalidWorkerIngressFirewall", - Field: getFieldYamlTag(c, "Worker.IngressFirewall"), - Message: formatError(multierror.Append(err)), - }) - } - } - - if len(c.Worker.ExtraManifests) > 0 { - if err := checkExtraManifests(c.Worker.ExtraManifests); err != nil { - result = result.Append(&Error{ - Kind: "InvalidWorkerExtraManifests", - Field: getFieldYamlTag(c, "Worker.ExtraManifests"), - Message: formatError(multierror.Append(err)), - }) - } - } - return result -} - func checkNodeRequiredCfg(node Node, idx int, result *Errors) *Errors { if node.Hostname == "" { e := &Error{ @@ -393,35 +327,6 @@ func checkNodeMachineFiles(node Node, idx int, result *Errors) *Errors { return result } -func checkNodeExtensions(node Node, idx int, errs *Errors, warns *Warnings) (*Errors, *Warnings) { - if len(node.Extensions) > 0 { - warns.Append(&Warning{ - Kind: "DeprecatedNodeExtensions", - Field: getNodeFieldYamlTag(node, idx, "Extensions"), - Message: formatWarning("`extensions` is deprecated, please use `schematic.customization.systemExtensions` instead"), - }) - var messages *multierror.Error - extensions := map[string]struct{}{} - - for _, ext := range node.Extensions { - if _, exists := extensions[ext.Image()]; exists { - messages = multierror.Append(messages, fmt.Errorf("duplicate system extension %q", ext.Image())) - } - extensions[ext.Image()] = struct{}{} - } - - if messages.ErrorOrNil() != nil { - return errs.Append(&Error{ - Kind: "InvalidNodeExtensions", - Field: getNodeFieldYamlTag(node, idx, "Extensions"), - Message: formatError(messages), - }), warns - } - } - - return errs, warns -} - func checkNodeSchematic(node Node, idx int, talosVersion string, result *Errors) *Errors { var messages *multierror.Error extensions := map[string]struct{}{} @@ -537,23 +442,42 @@ func checkNodeNetworkInterfaces(node Node, idx int, result *Errors) *Errors { return result } -func checkNodeConfigPatches(node Node, idx int, result *Errors) *Errors { - if len(node.ConfigPatches) > 0 { - if !isRFC6902List(node.ConfigPatches) { - e := fmt.Errorf("doesn't look like list of RFC6902 JSON patches") - return result.Append(&Error{ - Kind: "InvalidNodeConfigPatches", - Field: getNodeFieldYamlTag(node, idx, "ConfigPatches"), - Message: formatError(multierror.Append(e)), - }) - } - } - return result -} - func checkNodeIngressFirewall(node Node, idx int, result *Errors) *Errors { if node.IngressFirewall != nil { - messages := checkIngressFirewall(node.IngressFirewall) + var messages *multierror.Error + + if len(node.IngressFirewall.NetworkRules) > 0 { + for k, v := range node.IngressFirewall.NetworkRules { + if v.Name == "" { + messages = multierror.Append(messages, fmt.Errorf("rules[%d]: name is required", k)) + } + + if !v.PortSelector.Protocol.IsAProtocol() { + messages = multierror.Append(messages, fmt.Errorf("rules[%d]: %q is not a valid protocol", k, v.PortSelector.Protocol)) + } + + if len(v.PortSelector.Ports) == 0 { + messages = multierror.Append(messages, fmt.Errorf("rules[%d]: portSelector.ports is required", k)) + } + + if err := v.PortSelector.Ports.Validate(); err != nil { + messages = multierror.Append(messages, fmt.Errorf("rules[%d]: %q", k, err)) + } + + for _, rule := range v.Ingress { + if !rule.Subnet.IsValid() { + messages = multierror.Append(messages, fmt.Errorf("rules[%d]: invalid subnet: %s", k, rule.Subnet)) + } + if !rule.Except.IsZero() && !rule.Except.IsValid() { + messages = multierror.Append(messages, fmt.Errorf("rules[%d]: invalid except: %s", k, rule.Except)) + } + } + } + } + if !node.IngressFirewall.DefaultAction.IsADefaultAction() { + messages = multierror.Append(messages, fmt.Errorf("%q is not a valid default action", node.IngressFirewall.DefaultAction)) + } + if messages.ErrorOrNil() != nil { return result.Append(&Error{ Kind: "InvalidNodeIngressFirewall", @@ -567,7 +491,13 @@ func checkNodeIngressFirewall(node Node, idx int, result *Errors) *Errors { func checkNodeExtraManifests(node Node, idx int, result *Errors) *Errors { if len(node.ExtraManifests) > 0 { - messages := checkExtraManifests(node.ExtraManifests) + var messages *multierror.Error + + for k, manifest := range node.ExtraManifests { + if _, osErr := os.Stat(manifest); osErr != nil { + messages = multierror.Append(messages, fmt.Errorf("extraManifests[%d], %q", k, osErr)) + } + } if messages.ErrorOrNil() != nil { return result.Append(&Error{ @@ -576,62 +506,11 @@ func checkNodeExtraManifests(node Node, idx int, result *Errors) *Errors { Message: formatError(messages), }) } - } return result } -func checkIngressFirewall(ifCfg *IngressFirewall) *multierror.Error { - var messages *multierror.Error - - if len(ifCfg.NetworkRules) > 0 { - for k, v := range ifCfg.NetworkRules { - if v.Name == "" { - messages = multierror.Append(messages, fmt.Errorf("rules[%d]: name is required", k)) - } - - if !v.PortSelector.Protocol.IsAProtocol() { - messages = multierror.Append(messages, fmt.Errorf("rules[%d]: %q is not a valid protocol", k, v.PortSelector.Protocol)) - } - - if len(v.PortSelector.Ports) == 0 { - messages = multierror.Append(messages, fmt.Errorf("rules[%d]: portSelector.ports is required", k)) - } - - if err := v.PortSelector.Ports.Validate(); err != nil { - messages = multierror.Append(messages, fmt.Errorf("rules[%d]: %q", k, err)) - } - - for _, rule := range v.Ingress { - if !rule.Subnet.IsValid() { - messages = multierror.Append(messages, fmt.Errorf("rules[%d]: invalid subnet: %s", k, rule.Subnet)) - } - if !rule.Except.IsZero() && !rule.Except.IsValid() { - messages = multierror.Append(messages, fmt.Errorf("rules[%d]: invalid except: %s", k, rule.Except)) - } - } - } - } - if !ifCfg.DefaultAction.IsADefaultAction() { - messages = multierror.Append(messages, fmt.Errorf("%q is not a valid default action", ifCfg.DefaultAction)) - } - - return messages -} - -func checkExtraManifests(extraManifests []string) *multierror.Error { - var messages *multierror.Error - - for k, manifest := range extraManifests { - if _, osErr := os.Stat(manifest); osErr != nil { - messages = multierror.Append(messages, fmt.Errorf("extraManifests[%d], %q", k, osErr)) - } - } - - return messages -} - var hostnamePattern = sync.OnceValue(func() *regexp.Regexp { return regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`) }) @@ -706,9 +585,9 @@ func formatError(e *multierror.Error) *multierror.Error { return e } -func formatWarning(w string) string { - return fmt.Sprintf(" * WARNING: %s", w) -} +// func formatWarning(w string) string { +// return fmt.Sprintf(" * WARNING: %s", w) +// } func getNodeFieldYamlTag(node Node, idx int, fieldPath string) string { return "nodes[" + fmt.Sprintf("%v", idx) + "]." + getFieldYamlTag(node, fieldPath) diff --git a/pkg/generate/config.go b/pkg/generate/config.go index 7710ab60..a96a333b 100644 --- a/pkg/generate/config.go +++ b/pkg/generate/config.go @@ -35,20 +35,6 @@ func GenerateConfig(c *config.TalhelperConfig, dryRun bool, outDir, secretFile, return err } - if node.InlinePatch != nil { - cfg, err = patcher.YAMLInlinePatcher(node.InlinePatch, cfg) - if err != nil { - return err - } - } - - if len(node.ConfigPatches) != 0 { - cfg, err = patcher.YAMLPatcher(node.ConfigPatches, cfg) - if err != nil { - return err - } - } - if len(node.Patches) != 0 { cfg, err = patcher.PatchesPatcher(node.Patches, cfg) if err != nil { @@ -56,34 +42,6 @@ func GenerateConfig(c *config.TalhelperConfig, dryRun bool, outDir, secretFile, } } - if node.ControlPlane { - cfg, err = patcher.YAMLInlinePatcher(c.ControlPlane.InlinePatch, cfg) - if err != nil { - return err - } - cfg, err = patcher.YAMLPatcher(c.ControlPlane.ConfigPatches, cfg) - if err != nil { - return err - } - cfg, err = patcher.PatchesPatcher(c.ControlPlane.Patches, cfg) - if err != nil { - return err - } - } else { - cfg, err = patcher.YAMLInlinePatcher(c.Worker.InlinePatch, cfg) - if err != nil { - return err - } - cfg, err = patcher.YAMLPatcher(c.Worker.ConfigPatches, cfg) - if err != nil { - return err - } - cfg, err = patcher.PatchesPatcher(c.Worker.Patches, cfg) - if err != nil { - return err - } - } - if len(c.Patches) > 0 { cfg, err = patcher.PatchesPatcher(c.Patches, cfg) if err != nil { @@ -117,38 +75,6 @@ func GenerateConfig(c *config.TalhelperConfig, dryRun bool, outDir, secretFile, cfg = append(cfg, content...) } - if node.ControlPlane { - if c.ControlPlane.IngressFirewall != nil { - nc, err := talos.GenerateNetworkConfigBytes(c.ControlPlane.IngressFirewall) - if err != nil { - return err - } - cfg = append(cfg, nc...) - } - if len(c.ControlPlane.ExtraManifests) > 0 { - content, err := combineExtraManifests(c.ControlPlane.ExtraManifests) - if err != nil { - return err - } - cfg = append(cfg, content...) - } - } else { - if c.Worker.IngressFirewall != nil { - nc, err := talos.GenerateNetworkConfigBytes(c.Worker.IngressFirewall) - if err != nil { - return err - } - cfg = append(cfg, nc...) - } - if len(c.Worker.ExtraManifests) > 0 { - content, err := combineExtraManifests(c.Worker.ExtraManifests) - if err != nil { - return err - } - cfg = append(cfg, content...) - } - } - if !dryRun { err = dumpFile(cfgFile, cfg) if err != nil { diff --git a/pkg/talos/input.go b/pkg/talos/input.go index 73a010e9..ca3e292d 100644 --- a/pkg/talos/input.go +++ b/pkg/talos/input.go @@ -67,7 +67,7 @@ func parseOptions(c *config.TalhelperConfig, versionContract *tconfig.VersionCon opts = append(opts, generate.WithVersionContract(versionContract)) opts = append(opts, generate.WithSecretsBundle(sb)) - opts = append(opts, generate.WithInstallImage(c.GetInstallerURL())) + opts = append(opts, generate.WithInstallImage("ghcr.io/siderolabs/installer:"+c.GetTalosVersion())) if c.AllowSchedulingOnMasters || c.AllowSchedulingOnControlPlanes { opts = append(opts, generate.WithAllowSchedulingOnControlPlanes(true)) diff --git a/pkg/talos/nodeconfig.go b/pkg/talos/nodeconfig.go index bd4d343d..f179b80a 100644 --- a/pkg/talos/nodeconfig.go +++ b/pkg/talos/nodeconfig.go @@ -92,10 +92,6 @@ func applyNodeOverride(node *config.Node, cfg taloscfg.Provider) taloscfg.Provid cfg.RawV1Alpha1().MachineConfig.MachineNodeTaints = node.NodeTaints } - if len(node.Extensions) > 0 { - cfg.RawV1Alpha1().MachineConfig.MachineInstall.InstallExtensions = node.Extensions - } - if len(node.MachineFiles) > 0 { cfg.RawV1Alpha1().MachineConfig.MachineFiles = node.MachineFiles } diff --git a/pkg/talos/nodeconfig_test.go b/pkg/talos/nodeconfig_test.go index 3fddd20b..189f3406 100644 --- a/pkg/talos/nodeconfig_test.go +++ b/pkg/talos/nodeconfig_test.go @@ -95,11 +95,6 @@ nodes: expectedNode1Hostname := "node1" expectedNode1InstallDisk := "/dev/sda" expectedNode1DisableSearchDomain := true - expectedNode1Extensions := []v1alpha1.InstallExtensionConfig{ - { - ExtensionImage: "ghcr.io/siderolabs/tailscale:1.44.0", - }, - } expectedNode1MachineFiles := []*v1alpha1.MachineFile{ { FileContent: "TS_AUTHKEY=123456", @@ -163,7 +158,6 @@ nodes: compare(cpCfg.MachineNetwork.Hostname(), expectedNode1Hostname, t) compare(cpCfg.MachineInstall.InstallDisk, expectedNode1InstallDisk, t) compare(cpCfg.MachineNetwork.DisableSearchDomain(), expectedNode1DisableSearchDomain, t) - compare(cpCfg.MachineInstall.InstallExtensions, expectedNode1Extensions, t) compare(cpCfg.MachineFiles, expectedNode1MachineFiles, t) compare(cpCfg.MachineNetwork.NetworkInterfaces, expectedNode1NetworkInterfaces, t) compare(cpCfg.MachineKernel, expectedNode1KernelModules, t)