Compare commits

18 Commits

Author SHA1 Message Date
Dreaded_X 8b806b7a38 Improved how the cluster is defined 2025-11-09 03:22:29 +01:00
Dreaded_X 20e96b33df Fixed wornding of control plane 2025-11-09 03:14:15 +01:00
Dreaded_X 2b7434c0e7 Removed unneeded --- from patches 2025-11-09 03:11:20 +01:00
Dreaded_X 08d73b95d4 Added source script to set environment variables 2025-11-09 03:07:20 +01:00
Dreaded_X 2cca38c860 Made repo root available for templates
This allows for embedding the repo root inside of, for example, scripts
to make them function properly no matter where they are run from.
2025-11-09 03:03:46 +01:00
Dreaded_X d2a1eca146 Find root of repo that contains the actual script
This makes it possible to run the render script from anywhere and have
it still function correctly.
2025-11-09 03:03:21 +01:00
Dreaded_X 0049b5cb46 Moved logic for getting clusters to render script 2025-11-09 02:58:01 +01:00
Dreaded_X 3f8389ddd2 Made yaml template loader more generic 2025-11-09 02:26:35 +01:00
Dreaded_X dac2864b2d Store template resolved nodes back in nodes object 2025-11-09 02:16:10 +01:00
Dreaded_X 85368a3126 Added template for config generation script 2025-11-09 02:16:10 +01:00
Dreaded_X 18b5d8fd18 Store patches as objects instead of strings 2025-11-09 02:05:08 +01:00
Dreaded_X 21ae5bc2c4 Added node types 2025-11-09 01:42:52 +01:00
Dreaded_X 17b0b05410 Added kubernetes version 2025-11-09 01:42:44 +01:00
Dreaded_X 8832371b99 Added jinja2 do extensions 2025-11-09 01:41:57 +01:00
Dreaded_X a9fbf9aad8 Use consistent capitalization 2025-11-08 22:27:37 +01:00
Dreaded_X 4f072d7cb7 Moved around node config params 2025-11-08 22:23:43 +01:00
Dreaded_X 235ab5add7 Make python script runnable from anywhere 2025-11-08 21:47:45 +01:00
Dreaded_X b0ac551c21 Render all template using python and jinja 2025-11-08 21:47:35 +01:00
67 changed files with 472 additions and 456 deletions
+5 -1
View File
@@ -5,4 +5,8 @@ indent_style = tab
[*.yaml]
indent_style = space
indent_size = 2
indent_size = 4
[{*.py,tools/render}]
indent_style = space
indent_size = 4
-1
View File
@@ -1,2 +1 @@
*.key filter=git-crypt diff=git-crypt
secrets.yaml filter=git-crypt diff=git-crypt
-1
View File
@@ -1,4 +1,3 @@
.ipxe/
rendered/
configs/
.vagrant/
-67
View File
@@ -1,67 +0,0 @@
default_install_hook_types:
- pre-commit
- commit-msg
default_stages:
- pre-commit
repos:
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes
- repo: builtin
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-added-large-files
- id: check-merge-conflict
- id: check-executables-have-shebangs
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.37.1
hooks:
- id: check-jsonschema
files: ^talos/patches/.*\.y(a?)ml$
args:
[
"--schemafile",
"https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json",
]
- id: check-jsonschema
files: ^talos/nodes/.*\.y(a?)ml$
args:
[
"--schemafile",
"https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json",
]
- id: check-jsonschema
files: ^talos/clusters/.*\.y(a?)ml$
args:
[
"--schemafile",
"https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/cluster.json",
]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier
- repo: https://github.com/crate-ci/typos
rev: v1.40.0
hooks:
- id: typos
- repo: https://github.com/sirwart/ripsecrets
rev: v0.1.11
hooks:
- id: ripsecrets-system
- repo: https://github.com/crate-ci/committed
rev: v1.1.8
hooks:
- id: committed
-2
View File
@@ -1,2 +0,0 @@
secrets.yaml
*.key
-6
View File
@@ -65,9 +65,3 @@ Upgrading talos or changing the schematic:
```bash
talosctl upgrade --nodes <node_id> --image factory.talos.dev/metal-installer/<schematic_id>:<version>
```
To upgrade kubernetes or inline manifests, first apply the updated controlplane configs, then run:
```bash
talosctl upgrade-k8s
```
Vendored
-28
View File
@@ -1,28 +0,0 @@
Vagrant.configure("2") do |config|
config.vm.define "talos-vm" do |vm|
vm.vm.network :private_network,
:type => "dhcp",
:libvirt__network_address => "192.168.1.0",
:libvirt__netmask => "255.255.255.0",
# :libvirt__dhcp_bootp_file => "ipxe.pxe"
:libvirt__dhcp_bootp_file => "http://192.168.1.1:8000/ipxe.pxe"
vm.vm.hostname = "talos"
vm.vm.provider :libvirt do |domain|
domain.cpus = 6
domain.memory = 16 * 1024
domain.storage :file, :size => '100G', :type => 'raw'
domain.mgmt_attach = false
domain.boot "hd"
domain.boot "network"
domain.sysinfo = {
"system": {
"serial": "talos-vm"
}
}
end
end
end
-2
View File
@@ -1,2 +0,0 @@
style = "conventional"
ignore_author_re = "Flux"
+2
View File
@@ -0,0 +1,2 @@
dhcp:
tftpIp: 10.0.0.3
-2
View File
@@ -1,2 +0,0 @@
[env]
VAGRANT_DEFAULT_PROVIDER = "libvirt"
+29
View File
@@ -0,0 +1,29 @@
schematicId: !schematic default
arch: amd64
talosVersion: v1.11.3
kubernesVersion: v1.34.1
kernelArgs:
- talos.platform=metal
- console=tty0
- init_on_alloc=1
- init_on_free=1
- slab_nomerge
- pti=on
- consoleblank=0
- nvme_core.io_timeout=4294967295
- printk.devkmsg=on
- selinux=1
- lockdown=confidentiality
extraKernelArgs: []
dns:
- 1.1.1.1
- 8.8.8.8
ntp: nl.pool.ntp.org
install: true
patches:
- !patch hostname
- !patch install-disk
- !patch network
- !patch vip
patchesControlPlane:
- !patch allow-control-plane-workloads
+6
View File
@@ -0,0 +1,6 @@
netmask: 255.255.252.0
gateway: 10.0.0.1
installDisk: /dev/sda
cluster:
name: hellas
controlPlaneIp: 10.0.2.1
+4
View File
@@ -0,0 +1,4 @@
serial: 5CZ7NX2
interface: enp2s0
ip: 10.0.0.202
type: "controlplane"
+4
View File
@@ -0,0 +1,4 @@
serial: F3PKRH2
interface: enp3s0
ip: 10.0.0.201
type: "controlplane"
+4
View File
@@ -0,0 +1,4 @@
serial: J33CHY2
interface: enp2s0
ip: 10.0.0.203
type: "controlplane"
+6
View File
@@ -0,0 +1,6 @@
netmask: 255.255.255.0
gateway: 192.168.1.1
installDisk: /dev/vda
cluster:
name: testing
controlPlaneIp: 192.168.1.100
+4
View File
@@ -0,0 +1,4 @@
serial: talos-vm
interface: eth0
ip: 192.168.1.2
type: "controlplane"
@@ -0,0 +1,2 @@
cluster:
allowSchedulingOnControlPlanes: true
+3
View File
@@ -0,0 +1,3 @@
machine:
network:
hostname: {{hostname}}
+3
View File
@@ -0,0 +1,3 @@
machine:
install:
disk: {{installDisk}}
+10
View File
@@ -0,0 +1,10 @@
machine:
network:
interfaces:
- interface: {{interface}}
dhcp: false
addresses:
- {{ip}}
routes:
- network: 0.0.0.0/0
gateway: {{gateway}}
+6
View File
@@ -0,0 +1,6 @@
machine:
network:
interfaces:
- interface: {{interface}}
vip:
ip: {{cluster.controlPlaneIp}}
+4
View File
@@ -0,0 +1,4 @@
PyYAML==6.0.3
requests==2.32.5
Jinja2==3.1.6
GitPython==3.1.45
+7
View File
@@ -0,0 +1,7 @@
customization:
systemExtensions:
officialExtensions:
- siderolabs/iscsi-tools
- siderolabs/util-linux-tools
- siderolabs/intel-ucode
- siderolabs/i915
BIN
View File
Binary file not shown.
-52
View File
@@ -1,52 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/cluster.json
version:
kubernetes: 1.35.3
talos: 1.12.6
base:
kernelArgs:
- talos.platform=metal
- console=tty0
- init_on_alloc=1
- init_on_free=1
- slab_nomerge
- pti=on
- consoleblank=0
- nvme_core.io_timeout=4294967295
- printk.devkmsg=on
- selinux=1
- lockdown=confidentiality
patches:
all:
- system/hostname.yaml
- system/install-disk.yaml
- system/network.yaml.jinja
- system/ntp.yaml
- system/dns.yaml.jinja
- networking/vip.yaml
- networking/tailscale.yaml
- networking/cilium.yaml
- spegel.yaml
- storage/longhorn.yaml
- storage/longhorn/user-volume.yaml
- storage/local-path-provisioner/user-volume.yaml
- storage/limit-ephemeral.yaml
- metrics/all.yaml
controlPlane:
- system/allow-control-plane-workloads.yaml
- sops.yaml
- flux/cluster-variables.yaml
- metrics/control-plane.yaml
- networking/gateway-api.yaml
default:
arch: amd64
schematic: default.yaml
network:
dns:
- 1.1.1.1
- 8.8.8.8
tailscale:
server: https://headscale.huizinga.dev
ntp: nl.pool.ntp.org
install:
auto: true
-19
View File
@@ -1,19 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/cluster.json
clusterEnv: staging
controlPlaneIp: 192.168.1.100
secretsFile: testing/secrets.yaml
nodes:
- testing/talos-vm
default:
network:
interface: ens5
netmask: 255.255.255.0
gateway: 192.168.1.1
tailscale:
authKey:
file: testing/tailscale.key
sops:
file: testing/age.key
install:
disk: /dev/vda
-20
View File
@@ -1,20 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/cluster.json
clusterEnv: production
controlPlaneIp: 10.0.2.1
secretsFile: titan/secrets.yaml
nodes:
- titan/hyperion
- titan/helios
- titan/selene
default:
network:
netmask: 255.255.252.0
gateway: 10.0.0.1
tailscale:
authKey:
file: testing/tailscale.key
sops:
file: titan/age.key
install:
disk: /dev/sda
-8
View File
@@ -1,8 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: controlPlane
install:
serial: talos-vm
network:
ip: 192.168.1.2
tailscale:
advertiseRoutes: true
-7
View File
@@ -1,7 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: controlPlane
install:
serial: 5CZ7NX2
network:
interface: enp2s0
ip: 10.0.0.202
-7
View File
@@ -1,7 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: controlPlane
install:
serial: F3PKRH2
network:
interface: enp3s0
ip: 10.0.0.201
-7
View File
@@ -1,7 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: controlPlane
install:
serial: J33CHY2
network:
interface: enp2s0
ip: 10.0.0.203
-18
View File
@@ -1,18 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
inlineManifests:
- name: cluster-variables
contents: |
---
apiVersion: v1
kind: Namespace
metadata:
name: flux-system
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cluster-variables
namespace: flux-system
data:
cluster_env: {{ cluster.clusterEnv }}
-5
View File
@@ -1,5 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
kubelet:
extraArgs:
rotate-server-certificates: "true"
-5
View File
@@ -1,5 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
extraManifests:
- https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/main/deploy/standalone-install.yaml
- https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
-12
View File
@@ -1,12 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
features:
hostDNS:
# This option is enabled by default and causes issues with cilium
forwardKubeDNSToHost: false
cluster:
network:
cni:
name: none
proxy:
disabled: true
@@ -1,4 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
extraManifests:
- https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.1/standard-install.yaml
-8
View File
@@ -1,8 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: ExtensionServiceConfig
name: tailscale
environment:
- TS_AUTHKEY={{ node.network.tailscale.authKey }}
- TS_EXTRA_ARGS={% if node.network.tailscale.server %}--login-server {{ node.network.tailscale.server }}{% endif +%}
- TS_ROUTES={% if node.network.tailscale.advertiseRoutes %}{{node.network.ip}}/{{ node.network.netmask | to_prefix }}{% endif %}
-5
View File
@@ -1,5 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: Layer2VIPConfig
name: "{{ cluster.controlPlaneIp }}"
link: "{{ node.network.interface }}"
-18
View File
@@ -1,18 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
inlineManifests:
- name: sops-key
contents: |
apiVersion: v1
kind: Namespace
metadata:
name: flux-system
---
apiVersion: v1
kind: Secret
metadata:
name: sops-gpg
namespace: flux-system
stringData:
age.agekey: |
{{ node.sops | indent(6*2) }}
-8
View File
@@ -1,8 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
files:
- path: /etc/cri/conf.d/20-customization.part
op: create
content: |
[plugins."io.containerd.cri.v1.images"]
discard_unpacked_layers = false
@@ -1,6 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: VolumeConfig
name: EPHEMERAL
provisioning:
maxSize: 30GB
@@ -1,9 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: UserVolumeConfig
name: local-path-provisioner
provisioning:
diskSelector:
match: system_disk
grow: true
maxSize: 10GB
-11
View File
@@ -1,11 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
kubelet:
extraMounts:
- destination: /var/lib/longhorn
type: bind
source: /var/lib/longhorn
options:
- bind
- rshared
- rw
@@ -1,20 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: UserVolumeConfig
name: longhorn
provisioning:
diskSelector:
match: system_disk
grow: true
maxSize: 2000GB
# # yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
# apiVersion: v1alpha1
# kind: UserVolumeConfig
# name: longhorn
# # We want to take the whole distk
# # TODO: Add second disk to virtual machine
# volumeType: "disk"
# provisioning:
# diskSelector:
# # TODO: UPDATE THIS
# # match: system_disk
-17
View File
@@ -1,17 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
# This is only needed on nodes that will have storage
sysctls:
vm.nr_hugepages: "1024"
nodeLabels:
openebs.io/engine: mayastor
# This is needed on ALL nodes
kubelet:
extraMounts:
- destination: /var/local
type: bind
source: /var/local
options:
- bind
- rshared
- rw
@@ -1,3 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
allowSchedulingOnControlPlanes: true
-7
View File
@@ -1,7 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: ResolverConfig
nameservers:
{% for dns in node.network.dns %}
- address: {{ dns }}
{% endfor %}
-5
View File
@@ -1,5 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: HostnameConfig
hostname: "{{node.hostname}}"
auto: "off"
-4
View File
@@ -1,4 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
install:
disk: "{{node.install.disk}}"
-10
View File
@@ -1,10 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: LinkConfig
name: "{{node.network.interface}}"
up: true
mtu: 9000
addresses:
- address: "{{node.network.ip}}/{{ node.network.netmask | to_prefix }}"
routes:
- gateway: "{{node.network.gateway}}"
-5
View File
@@ -1,5 +0,0 @@
apiVersion: v1alpha1
kind: TimeSyncConfig
ntp:
servers:
- "{{ node.ntp }}"
-8
View File
@@ -1,8 +0,0 @@
customization:
systemExtensions:
officialExtensions:
- siderolabs/iscsi-tools
- siderolabs/util-linux-tools
- siderolabs/intel-ucode
- siderolabs/i915
- siderolabs/tailscale
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9 -9
View File
@@ -1,5 +1,5 @@
{% set httpUrl = "http://192.168.1.1:8000" -%}
#!ipxe
dhcp
echo Starting ${serial}
@@ -8,15 +8,15 @@ echo Starting ${serial}
goto node_${serial} || exit
# Default behavior (non install mode) is to exit iPXE script
{% for cluster in clusters%}
{% for node in cluster.nodes %}
{%- if node.install.serial -%}
# {{ cluster.name }}/{{ node.hostname }}
:node_{{ node.install.serial }}
{% for node in nodes %}
{%- if node.install -%}
# {{ node.filename }}
:node_{{ node.serial }}
{% set ipArg = "ip=" ~ node.ip ~ "::" ~ node.gateway ~ ":" ~ node.netmask ~ ":" ~ node.hostname ~ ":" ~ node.interface ~ "::" ~ node.dns[0] ~ ":" ~ node.dns[1] ~ ":" ~ node.ntp -%}
{% set kernelArgs = ipArg ~ " " ~ node.kernelArgs ~ " " ~ node.extraKernelArgs -%}
imgfree
kernel https://pxe.factory.talos.dev/image/{{ node.schematic }}/v{{ cluster.version.talos }}/kernel-{{ node.arch }} {{ node.kernelArgs|join(" ") }} {% if node.install.auto %}talos.config={{httpUrl}}/configs/{{cluster.name}}/{{node.hostname}}.yaml{% endif +%}
initrd https://pxe.factory.talos.dev/image/{{ node.schematic }}/v{{ cluster.version.talos }}/initramfs-{{ node.arch }}.xz
kernel https://pxe.factory.talos.dev/image/{{ node.schematicId }}/{{ node.talosVersion }}/kernel-{{ node.arch }} {{ kernelArgs }}
initrd https://pxe.factory.talos.dev/image/{{ node.schematicId }}/{{ node.talosVersion }}/initramfs-{{ node.arch }}.xz
boot
{% endif %}
{% endfor %}
{% endfor %}
+1 -1
View File
@@ -1,4 +1,4 @@
{% set tftpIp = "192.168.1.1" -%}
{% set tftpIp = config.dhcp.tftpIp -%}
enable-tftp
tftp-root=/tftproot
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT={{ root }}
CONFIGS=${ROOT}/configs
# Generate the configuration for each node
{% for node in nodes -%}
talosctl gen config {{ node.cluster.name }} https://{{ node.cluster.controlPlaneIp }}:6443 -f \
--with-secrets ${ROOT}/secrets.yaml \
--talos-version {{ node.talosVersion }} \
--kubernetes-version {{ node.kubernesVersion }} \
--output-types {{ node.type }} \
--install-image factory.talos.dev/metal-installer/{{ node.schematicId }}:{{ node.talosVersion }} \
{% for patch in node.patches -%}
{# The double call to tojson is needed to properly escape the patch (object -> json -> string) -#}
--config-patch {{ patch|tojson|tojson }} \
{% endfor -%}
{% for patch in node.patchesControlPlane -%}
--config-patch-control-plane {{ patch|tojson|tojson }} \
{% endfor -%}
--with-docs=false \
--with-examples=false \
-o ${CONFIGS}/{{ node.filename }}.yaml
{% endfor %}
# Generate the talosconfig file for each cluster
{% for cluster in clusters -%}
talosctl gen config {{ cluster.name }} https://{{ cluster.controlPlaneIp }}:6443 -f \
--with-secrets ${ROOT}/secrets.yaml \
--output-types talosconfig \
-o ${CONFIGS}/{{ cluster.name }}/talosconfig
{% endfor %}
# Create merged talosconfig
export TALOSCONFIG=${CONFIGS}/talosconfig
rm -f ${TALOSCONFIG}
{% for cluster in clusters -%}
talosctl config merge ${CONFIGS}/{{ cluster.name }}/talosconfig
{% endfor %}
+5 -1
View File
@@ -1,2 +1,6 @@
export TALOSCONFIG={{ root }}/configs/talosconfig
export KUBECONFIG={{ clusters|map(attribute='name')|kubeconfig|join(":") }}
{% set paths = [] %}
{%- for cluster in clusters -%}
{%- do paths.append(root ~ "/configs/" ~ cluster.name ~ "/kubeconfig") -%}
{% endfor -%}
export KUBECONFIG={{ paths|join(":") }}
Executable
+159
View File
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
# Adapted from: https://enix.io/en/blog/pxe-talos/
import functools
import json
import pathlib
import sys
import git
import requests
import yaml
from jinja2 import Environment, FileSystemLoader, StrictUndefined, Template
REPO = git.Repo(sys.path[0], search_parent_directories=True)
assert REPO.working_dir is not None
ROOT = pathlib.Path(REPO.working_dir)
NODES = ROOT.joinpath("nodes")
SCHEMATICS = ROOT.joinpath("schematics")
RENDERED = ROOT.joinpath("rendered")
EXTENSIONS = ["jinja2.ext.do"]
PATCHES = Environment(
loader=FileSystemLoader(ROOT.joinpath("patches")),
undefined=StrictUndefined,
extensions=EXTENSIONS,
)
TEMPLATES = Environment(
loader=FileSystemLoader(ROOT.joinpath("templates")),
undefined=StrictUndefined,
extensions=EXTENSIONS,
)
def render_templates(node: dict):
class Inner(json.JSONEncoder):
def default(self, o):
if isinstance(o, Template):
try:
rendered = o.render(node)
except Exception as e:
e.add_note(f"While rendering for: {node['hostname']}")
raise e
# Parse the rendered yaml
return yaml.safe_load(rendered)
return super().default(o)
return Inner
@functools.cache
def get_schematic_id(schematic: str):
"""Lookup the schematic id associated with a given schematic"""
r = requests.post("https://factory.talos.dev/schematics", data=schematic)
r.raise_for_status()
data = r.json()
return data["id"]
def schematic_constructor(loader: yaml.SafeLoader, node: yaml.nodes.ScalarNode):
"""Load specified schematic file and get the assocatied schematic id"""
schematic_name = loader.construct_yaml_str(node)
try:
schematic = SCHEMATICS.joinpath(schematic_name).with_suffix(".yaml").read_text()
return get_schematic_id(schematic)
except Exception:
raise yaml.MarkedYAMLError("Failed to load schematic", node.start_mark)
def template_constructor(environment: Environment):
def inner(loader: yaml.SafeLoader, node: yaml.nodes.ScalarNode):
patch_name = loader.construct_scalar(node)
try:
template = environment.get_template(f"{patch_name}.yaml")
return template
except Exception:
raise yaml.MarkedYAMLError("Failed to load patch", node.start_mark)
return inner
def get_loader():
"""Add special constructors to yaml loader"""
loader = yaml.SafeLoader
loader.add_constructor("!schematic", schematic_constructor)
loader.add_constructor("!patch", template_constructor(PATCHES))
return loader
@functools.cache
def get_defaults(directory: pathlib.Path, root: pathlib.Path):
"""Compute the defaults from the provided directory and parents."""
try:
with open(directory.joinpath("_defaults.yaml")) as fyaml:
yml_data = yaml.load(fyaml, Loader=get_loader())
except OSError:
yml_data = {}
# Stop recursion when reaching root directory
if directory != root:
return get_defaults(directory.parent, root) | yml_data
else:
return yml_data
def walk_files(root: pathlib.Path):
"""Get all files that do not start with and underscore"""
for dirpath, _dirnames, filenames in root.walk():
for fn in filenames:
if not fn.startswith("_"):
yield dirpath.joinpath(fn)
def main():
nodes = []
for fullname in walk_files(NODES):
filename = str(fullname.relative_to(NODES).parent) + "/" + fullname.stem
with open(fullname) as fyaml:
yml_data = yaml.load(fyaml, Loader=get_loader())
yml_data = get_defaults(fullname.parent, NODES) | yml_data
yml_data["hostname"] = fullname.stem
yml_data["filename"] = filename
nodes.append(yml_data)
# Quick and dirty way to resolve all the templates using a custom encoder
nodes = list(
map(
lambda node: json.loads(json.dumps(node, cls=render_templates(node))), nodes
)
)
# Get all clusters
# NOTE: This assumes that all nodes in the cluster use the same definition for the cluster
clusters = [
dict(s) for s in set(frozenset(node["cluster"].items()) for node in nodes)
]
with open(ROOT.joinpath("config.yaml")) as fyaml:
config = yaml.safe_load(fyaml)
RENDERED.mkdir(exist_ok=True)
for template_name in TEMPLATES.list_templates():
template = TEMPLATES.get_template(template_name)
rendered = template.render(
nodes=nodes, clusters=clusters, config=config, root=ROOT
)
with open(RENDERED.joinpath(template_name), "w") as f:
f.write(rendered)
if __name__ == "__main__":
main()
+6 -27
View File
@@ -6,7 +6,6 @@ IPXE_VERSION=b41bda4413bf286d7b7a449bc05e1531da1eec2e
IPXE_BIN=(bin/ipxe.pxe bin-x86_64-efi/ipxe.efi)
IPXE_DIR=${ROOT}/.ipxe/ipxe-${IPXE_VERSION}
HTTP_URL="http://192.168.1.1:8000"
function download_ipxe() {
base_dir=$(dirname ${IPXE_DIR})
@@ -26,8 +25,7 @@ function patch_ipxe() {
#!ipxe
dhcp
chain ${HTTP_URL}/boot.ipxe || shell
# chain boot.ipxe || shell
chain boot.ipxe || shell
EOF
cd - > /dev/null
@@ -44,6 +42,9 @@ function build_ipxe() {
cd - > /dev/null
}
function render() {
${ROOT}/tools/render
}
function host_tftp() {
TFTP_DIR=$(mktemp --tmpdir -d tftp.XXX)
@@ -63,30 +64,8 @@ function host_tftp() {
sudo in.tftpd --verbosity 100 --permissive -L --secure ${TFTP_DIR}
}
function host_http() {
HTTP_DIR=$(mktemp --tmpdir -d http.XXX)
chmod 755 ${HTTP_DIR}
function cleanup() {
rm -rf ${HTTP_DIR}
}
trap cleanup EXIT
ln -s ${ROOT}/rendered/boot.ipxe ${HTTP_DIR}
for bin in "${IPXE_BIN[@]}"; do
path=${IPXE_DIR}/src/${bin}
ln -s ${path} ${HTTP_DIR}
done
ln -s ${ROOT}/configs ${HTTP_DIR}
echo "Starting http"
cd ${HTTP_DIR}
python -m http.server 8000
cd -
}
download_ipxe
patch_ipxe
build_ipxe
crete generate
host_http
render
host_tftp
Executable
+154
View File
@@ -0,0 +1,154 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT=$(git rev-parse --show-toplevel)
VM_NAME="talos-vm"
VCPUS="2"
RAM_MB="2048"
DISK_GB="10"
NETWORK=talos
CONNECTION="qemu:///system"
function define_network() {
config_file=$(mktemp)
cat > ${config_file} << EOF
<network>
<name>${NETWORK}</name>
<bridge name="talos0" stp="on" delay="0"/>
<forward mode='nat'>
<nat/>
</forward>
<ip address="192.168.1.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.1.2" end="192.168.1.254"/>
<bootp file='ipxe.pxe'/>
</dhcp>
</ip>
</network>
EOF
function cleanup() {
rm ${config_file}
}
trap cleanup EXIT
if [[ $(virsh --connect="${CONNECTION}" net-list --all | grep -c "${NETWORK}") == "0" ]]; then
virsh --connect="${CONNECTION}" net-define "${config_file}"
virsh --connect="${CONNECTION}" net-start "${NETWORK}"
virsh --connect="${CONNECTION}" net-autostart "${NETWORK}"
fi
trap - EXIT
cleanup
}
function create() {
define_network
if [[ $(virsh --connect="${CONNECTION}" list --all | grep -c "${VM_NAME}") == "0" ]]; then
virt-install --connect="${CONNECTION}" --name="${VM_NAME}" --vcpus="${VCPUS}" --memory="${RAM_MB}" \
--os-variant="linux2022" \
--disk="size=${DISK_GB}" \
--pxe \
--sysinfo system.serial=${VM_NAME} \
--network network="${NETWORK}"
else
echo -n "VM already exists, start it with:
${0} start
"
exit -1
fi
}
function start() {
if [[ $(virsh --connect="${CONNECTION}" list --all | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" start ${VM_NAME}
virt-viewer --connect="${CONNECTION}" ${VM_NAME}
else
echo -n "VM doest not exists yet, create it with:
${0} create
"
exit -1
fi
}
function connect() {
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virt-viewer --connect="${CONNECTION}" ${VM_NAME}
else
echo "VM is not running"
exit -1
fi
}
function stop() {
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" shutdown ${VM_NAME}
WAIT=240
for i in $(seq 0 1 ${WAIT}); do
echo -en "\rWaiting for VM to shutdown... (${i}/${WAIT})"
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") == "0" ]]; then
echo -e "\nVM successfully shutdown"
exit
fi
sleep 1
done
echo -e "\nDestroying VM"
virsh --connect="${CONNECTION}" destroy ${VM_NAME}
else
echo "VM is not running"
exit -1
fi
}
function delete() {
if [[ $(virsh --connect="${CONNECTION}" list --all | grep -c "${VM_NAME}") > "0" ]]; then
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" destroy "${VM_NAME}"
fi
virsh --connect="${CONNECTION}" undefine "${VM_NAME}" --remove-all-storage
else
echo "VM doest not exists"
exit -1
fi
if [[ $(virsh --connect="${CONNECTION}" net-list --all | grep -c "${NETWORK}") > "0" ]]; then
virsh --connect="${CONNECTION}" net-destroy "${NETWORK}"
virsh --connect="${CONNECTION}" net-undefine "${NETWORK}"
fi
}
function help() {
echo -n "Available commands:
start
stop
remove
connect
"
}
COMMAND=${1:-}
case ${COMMAND} in
create)
create Create the vm and perform first install
;;
start)
start Start the vm
;;
stop)
stop Stop the vm
;;
delete)
delete Delete the vm
;;
connect)
connect Connect to an already running vm
;;
*)
help
;;
esac