Refactor ignition support during dracut
- Control root device using the node/profile root field - Add support for `/warewulf/wwinit.d` to run scripts during first stage - Move first-stage ignition support to `/warewulf/wwinit.d/` - Add `wwctl <node|profile> set --parttype` - Add overlay template functions `SystemdEscape` and `SystemdEscapePath` - Support configuring ignition with resources Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
@@ -11,6 +11,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Added override.conf for nm-wait-online-initrd.service with dracut.
|
- Added override.conf for nm-wait-online-initrd.service with dracut.
|
||||||
- Added userdocs for `wwctl node import` from yaml/csv.
|
- Added userdocs for `wwctl node import` from yaml/csv.
|
||||||
- Added uid, gid, and permissions to OverlayFile in REST API. #1925
|
- Added uid, gid, and permissions to OverlayFile in REST API. #1925
|
||||||
|
- (preview) Support provisioning to local storage with `wwctl <node|partition> set --root=/path/to/disk`. #1894
|
||||||
|
- (preview) Support disk provisioning during dracut boot stage with Ignition. #1894
|
||||||
|
- Support configuring Ignition with resources. #1894
|
||||||
|
- Added `wwctl <node|partition> set --parttype`. #1894
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -258,8 +262,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Add man pages and command reference to userdocs. #1488
|
- Add man pages and command reference to userdocs. #1488
|
||||||
- Document building images from scratch with Apptainer. #1485
|
- Document building images from scratch with Apptainer. #1485
|
||||||
- Added warewulfd:/overlay-file/{overlay}/{path...}?render={id}
|
- Added warewulfd:/overlay-file/{overlay}/{path...}?render={id}
|
||||||
- Add `wwct configure rsync` and the template `rsyncd.conf.ww`
|
|
||||||
- Make dracut aware of `root==perisitent` which will install the node image to the parition `rootfs`
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -199,7 +199,8 @@ install: build docs ## Install Warewulf from source
|
|||||||
(cd overlays && find * -path '*/internal' -prune -o -type d -exec mkdir -pv $(DESTDIR)$(DATADIR)/warewulf/overlays/{} \;)
|
(cd overlays && find * -path '*/internal' -prune -o -type d -exec mkdir -pv $(DESTDIR)$(DATADIR)/warewulf/overlays/{} \;)
|
||||||
(cd overlays && find * -path '*/internal' -prune -o -type l -exec cp -av {} $(DESTDIR)$(DATADIR)/warewulf/overlays/{} \;)
|
(cd overlays && find * -path '*/internal' -prune -o -type l -exec cp -av {} $(DESTDIR)$(DATADIR)/warewulf/overlays/{} \;)
|
||||||
chmod 0755 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/init
|
chmod 0755 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/init
|
||||||
chmod 0755 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/$(WWCLIENTDIR)/wwprescripts
|
chmod 0755 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/$(WWCLIENTDIR)/run-init
|
||||||
|
chmod 0755 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/$(WWCLIENTDIR)/run-wwinit.d
|
||||||
chmod 0600 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/$(WWCLIENTDIR)/config.ww
|
chmod 0600 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/$(WWCLIENTDIR)/config.ww
|
||||||
chmod 0600 $(DESTDIR)$(DATADIR)/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh*
|
chmod 0600 $(DESTDIR)$(DATADIR)/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh*
|
||||||
chmod 0644 $(DESTDIR)$(DATADIR)/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh*.pub.ww
|
chmod 0644 $(DESTDIR)$(DATADIR)/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh*.pub.ww
|
||||||
|
|||||||
@@ -1,52 +1,15 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
function setup_disks() {
|
get_stage() {
|
||||||
# format and prepare the disk(s)
|
stage="${1}"
|
||||||
/usr/bin/ignition --root=/sysroot --platform=metal --stage=disks || die "warewulf: failed to partition/format disk"
|
info "warewulf: loading stage: ${stage}"
|
||||||
# mount /dev/disk/by-partlabel/root "$NEWROOT" || die "warewulf: failed to mount new root"
|
# Load runtime overlay from a static privledged port.
|
||||||
/usr/bin/ignition --root=/sysroot --platform=metal --stage=mount || die "warewulf: failed to mount disk"
|
# Others use default settings.
|
||||||
|
localport=""
|
||||||
}
|
if [ "${stage}" = "runtime" ]; then
|
||||||
|
|
||||||
|
|
||||||
info "warewulf: Running warewulf v4 dracut init ${wwinit_persistent}"
|
|
||||||
archives="image system runtime"
|
|
||||||
if [ ${wwinit_persistent} -ne 1 ] ; then
|
|
||||||
info "warewulf: Mounting tmpfs at $NEWROOT"
|
|
||||||
mount -t tmpfs -o mpol=interleave ${wwinit_tmpfs_size_option} tmpfs "$NEWROOT"
|
|
||||||
else
|
|
||||||
info "warewulf: Using persistent setup ${wwinit_ignition}/ignition.json.ww?render=${wwinit_id}"
|
|
||||||
# get the igintion config and store in /run/igintion.ign, don't use igntion as so we can
|
|
||||||
# construct the download link
|
|
||||||
curl --location --silent --get ${localport} \
|
|
||||||
--retry 60 --retry-delay 1 \
|
|
||||||
"${wwinit_ignition}/ignition.json.ww?render=${wwinit_id}" -o /run/ignition-rootfs.json || die "warewulf: failed to fetch ignition configuration from ${wwinit_ignition}/ignition.json.ww?render=${wwinit_id}"
|
|
||||||
jq '.storage.filesystems |= map(select(.device=="/dev/disk/by-partlabel/rootfs").path="/")' /run/ignition-rootfs.json > /run/ignition.json || die "warewulf: failed to rewrite ignition configuration"
|
|
||||||
setup_disks
|
|
||||||
# if [ -e ${NEWROOT}/warewulf/ww_${wwinit_imagename} ] ; then
|
|
||||||
# info "warewulf: found ${NEWROOT}/warewulf/ww_${wwinit_imagename} running rsync for update"
|
|
||||||
# # we need only the overlays as we update the root
|
|
||||||
# # with rsync when the same image is used
|
|
||||||
# archives="system runtime"
|
|
||||||
# rsync -aux --delete --exclude=/proc/ --exclude=/sys/ --exclude=/dev rsync://${wwinit_ip}/${wwinit_imagename} $NEWROOT || die "warewulf: failed to run rsync"
|
|
||||||
# elif [ -e ${NEWROOT}/warewulf/ww_* ] ; then
|
|
||||||
# info "warewulf: found $(ls ${NEWROOT}/warewulf/ww_*) but image is ${wwinit_imagename}, wiping $NEWROOT"
|
|
||||||
# # we didn't have the wanted image but provisioned with warewulf, wipe the rootfs
|
|
||||||
# /usr/bin/ignition --root=/sysroot --platform=metal --stage=umount || die "warewulf: failed to mount disk"
|
|
||||||
# jq '.storage.filesystems |= map(select(.device=="/dev/disk/by-partlabel/rootfs").wipeFilesystem=true)' /run/ignition.json > /run/ignition.json.mod || die "couldn't reformation ignition configuration"
|
|
||||||
# mv /run/ignition.json.mod /run/ignition.json || die "warewulf: couldn't mv ignition configuration back"
|
|
||||||
# setup_disks
|
|
||||||
# fi
|
|
||||||
fi
|
|
||||||
for stage in $archives ; do
|
|
||||||
info "warewulf: Loading ${stage}"
|
|
||||||
# Load runtime overlay from a static privledged port.
|
|
||||||
# Others use default settings.
|
|
||||||
localport=""
|
|
||||||
if [[ "${stage}" == "runtime" ]] ; then
|
|
||||||
localport="--local-port 1-1023"
|
localport="--local-port 1-1023"
|
||||||
fi
|
fi
|
||||||
(
|
(
|
||||||
curl --location --silent --get ${localport} \
|
curl --location --silent --get ${localport} \
|
||||||
--retry 60 --retry-connrefused --retry-delay 1 \
|
--retry 60 --retry-connrefused --retry-delay 1 \
|
||||||
--data-urlencode "assetkey=${wwinit_assetkey}" \
|
--data-urlencode "assetkey=${wwinit_assetkey}" \
|
||||||
@@ -55,19 +18,29 @@ for stage in $archives ; do
|
|||||||
--data-urlencode "compress=gz" \
|
--data-urlencode "compress=gz" \
|
||||||
"${wwinit_uri}" \
|
"${wwinit_uri}" \
|
||||||
| gzip -d \
|
| gzip -d \
|
||||||
| cpio -im --directory="${NEWROOT}"
|
| cpio -ium --directory="${NEWROOT}"
|
||||||
) || die "Unable to load stage: ${stage}"
|
) || die "Unable to load stage: ${stage}"
|
||||||
done
|
}
|
||||||
|
|
||||||
if [ ${wwinit_persistent} -eq 1 ] ; then
|
mkdir /tmp/wwinit
|
||||||
# this avoids that ignition runs a second time
|
(
|
||||||
info "warewulf: removing ignition config from wwinit image"
|
# fetch the system overlay into /tmp/wwinit
|
||||||
rm -rf ${NEWROOT}/warewulf/ignition.json
|
local NEWROOT=/tmp/wwinit
|
||||||
echo "Container name of persistent install: ${wwinit_imagename}" > ${NEWROOT}/warewulf/ww_${wwinit_imagename}
|
get_stage "system"
|
||||||
echo "# created from ${wwinit_ignition_uri}/fstab.ww?render=${winit_id}" > ${NEWROOT}/etc/fstab
|
)
|
||||||
curl --location --silent --get ${localport} \
|
if [ -x /tmp/wwinit/warewulf/run-wwinit.d ]; then
|
||||||
--retry 60 --retry-delay 1 \
|
PREFIX=/tmp/wwinit /tmp/wwinit/warewulf/run-wwinit.d
|
||||||
"${wwinit_ignition}/fstab.ww?render=${wwinit_id}" >> ${NEWROOT}/etc/fstab || die "warewulf: failed to write correct fstab"
|
|
||||||
fi
|
fi
|
||||||
info "warewulf: Finished warewulf v4 dracut"
|
|
||||||
|
|
||||||
|
info "warewulf: mounting ${wwinit_root_device} at ${NEWROOT}"
|
||||||
|
(
|
||||||
|
if [ "${wwinit_root_device}" = "tmpfs" ]; then
|
||||||
|
mount -t tmpfs -o mpol=interleave ${wwinit_tmpfs_size_option} "${wwinit_root_device}" "${NEWROOT}"
|
||||||
|
else
|
||||||
|
mount "${wwinit_root_device}" "${NEWROOT}"
|
||||||
|
fi
|
||||||
|
) || die "warewulf: failed to mount ${wwinit_root_device} at ${NEWROOT}"
|
||||||
|
|
||||||
|
for stage in "image" "system" "runtime"; do
|
||||||
|
get_stage "${stage}"
|
||||||
|
done
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ check() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
depends() {
|
depends() {
|
||||||
echo network ignition
|
echo network
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
install() {
|
install() {
|
||||||
inst_multiple cpio curl dmidecode rsync jq
|
inst_multiple cpio curl dmidecode
|
||||||
inst_hook cmdline 30 "$moddir/parse-wwinit.sh"
|
inst_hook cmdline 30 "$moddir/parse-wwinit.sh"
|
||||||
inst_hook pre-mount 30 "$moddir/load-wwinit.sh"
|
inst_hook pre-mount 30 "$moddir/load-wwinit.sh"
|
||||||
if dracut_module_included "network-manager" && dracut_module_included "systemd"
|
if dracut_module_included "network-manager" && dracut_module_included "systemd"
|
||||||
|
|||||||
@@ -1,29 +1,42 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# root=wwinit
|
# root=wwinit|wwinit:*
|
||||||
|
|
||||||
[ -z "$root" ] && root=$(getarg root=)
|
[ -z "$root" ] && root=$(getarg root=)
|
||||||
|
|
||||||
if [ "${root}" = "wwinit" ] || [ "${root}" = "persistent" ] ; then
|
case "${root}" in
|
||||||
|
wwinit|wwinit:*)
|
||||||
info "warewulf: root=${root}"
|
info "warewulf: root=${root}"
|
||||||
export wwinit_uuid=$(dmidecode -s system-uuid)
|
|
||||||
export wwinit_assetkey=$(dmidecode -s chassis-asset-tag)
|
|
||||||
export wwinit_uri="$(getarg wwinit.uri)"
|
export wwinit_uri="$(getarg wwinit.uri)"
|
||||||
export wwinit_ignition="$(getarg wwinit.ignition)"
|
if [ -n "${wwinit_uri}" ]; then
|
||||||
export wwinit_ip="$(getarg wwinit.ip)"
|
info "warewulf: Found root=${root} and wwinit.uri=${wwinit_uri}. Will boot from Warewulf."
|
||||||
export wwinit_imagename="$(getarg wwinit.imagename)"
|
|
||||||
export wwinit_id=$(getarg wwinit.id)
|
|
||||||
wwinit_tmpfs_size=$(getarg wwinit.tmpfs.size=)
|
|
||||||
if [ -n "$wwinit_tmpfs_size" ] ; then
|
|
||||||
info "warewulf: wwinit.tmpfs.size=${wwinit_tmpfs_size}"
|
|
||||||
export wwinit_tmpfs_size_option="-o size=${wwinit_tmpfs_size}"
|
|
||||||
fi
|
|
||||||
if [ "${root}" = "persistent" ] ; then
|
|
||||||
export wwinit_persistent="1"; info "warewulf: wwinit_persistent=$wwinit_persistent"
|
|
||||||
fi
|
|
||||||
if [ -n "${wwinit_uri}" ] ; then
|
|
||||||
info "warewulf: Found root=${root} and a Warewulf server uri. Will boot from Warewulf."
|
|
||||||
rootok=1
|
rootok=1
|
||||||
else
|
else
|
||||||
die "warewulf: Found root=${root} but no Warewulf server uri. Cannot boot from Warewulf."
|
die "warewulf: Found root=${root} but no wwinit.uri. Cannot boot from Warewulf."
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
export wwinit_uuid=$(dmidecode -s system-uuid)
|
||||||
|
export wwinit_assetkey=$(dmidecode -s chassis-asset-tag)
|
||||||
|
|
||||||
|
wwinit_tmpfs_size="$(getarg wwinit.tmpfs.size)"
|
||||||
|
if [ -n "$wwinit_tmpfs_size" ]; then
|
||||||
|
export wwinit_tmpfs_size_option="-o size=${wwinit_tmpfs_size}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${root}" in
|
||||||
|
wwinit)
|
||||||
|
export wwinit_root_device="tmpfs"
|
||||||
|
;;
|
||||||
|
wwinit:*)
|
||||||
|
export wwinit_root_device="${root#wwinit:}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "${wwinit_root_device}" in
|
||||||
|
initramfs|rootfs)
|
||||||
|
info "warewulf: using tmpfs in stead of ${wwinit_root_device}"
|
||||||
|
export wwinit_root_device=tmpfs
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|||||||
@@ -31,10 +31,7 @@ smbios --type 3 --get-string 8 --set assetkey
|
|||||||
uri="(http,{{.Ipaddr}}:{{.Port}})/provision/${net_default_mac}?assetkey=${assetkey}"
|
uri="(http,{{.Ipaddr}}:{{.Port}})/provision/${net_default_mac}?assetkey=${assetkey}"
|
||||||
kernel="${uri}&stage=kernel"
|
kernel="${uri}&stage=kernel"
|
||||||
|
|
||||||
{{- $defentry := "singlestage" }}
|
set default={{ or .Tags.GrubMenuEntry "single-stage" }}
|
||||||
{{- if eq .Root "persistent"}}{{ $defentry = "dracut" }}{{ end }}
|
|
||||||
{{- if .Tags.GrubMenuEntry }}{{ $defentry = .Tags.GrubMenuEntry }}{{ end }}
|
|
||||||
set default={{ $defentry}}
|
|
||||||
set timeout=2
|
set timeout=2
|
||||||
|
|
||||||
menuentry "Single-stage boot" --id single-stage {
|
menuentry "Single-stage boot" --id single-stage {
|
||||||
@@ -117,13 +114,15 @@ menuentry "Single-stage boot (no compression)" --id single-stage-nocompress {
|
|||||||
echo "!! Unable to load images."
|
echo "!! Unable to load images."
|
||||||
echo "!! Rebooting in 15s..."
|
echo "!! Rebooting in 15s..."
|
||||||
echo "!!"
|
echo "!!"
|
||||||
|
sleep 15
|
||||||
|
reboot
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Booting..."
|
echo "Booting..."
|
||||||
boot
|
boot
|
||||||
}
|
}
|
||||||
|
|
||||||
menuentry "Two stage boot with dracut {{ if eq .Root "persistent"}}(persistent){{ end }}" --id dracut {
|
menuentry "Two stage boot with dracut" --id dracut {
|
||||||
echo "Warewulf Server:"
|
echo "Warewulf Server:"
|
||||||
echo "* Ipaddr: {{.Ipaddr}}"
|
echo "* Ipaddr: {{.Ipaddr}}"
|
||||||
echo "* Port: {{.Port}}"
|
echo "* Port: {{.Port}}"
|
||||||
@@ -143,18 +142,11 @@ menuentry "Two stage boot with dracut {{ if eq .Root "persistent"}}(persistent){
|
|||||||
|
|
||||||
wwinit_uri="http://{{.Ipaddr}}:{{.Port}}/provision/${net_default_mac}"
|
wwinit_uri="http://{{.Ipaddr}}:{{.Port}}/provision/${net_default_mac}"
|
||||||
net_args="rd.neednet=1 {{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}}"
|
net_args="rd.neednet=1 {{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}}"
|
||||||
{{- if eq .Root "persistent"}}
|
wwinit_args="root=wwinit:{{default "tmpfs" .Root}} wwinit.uri=${wwinit_uri} init=/warewulf/run-init"
|
||||||
ignition_args="wwinit.ignition=http://{{.Ipaddr}}:{{.Port}}/overlay-file/persistent wwinit.imagename={{ .ImageName }} wwinit.id={{ .Id }} wwinit.ip={{ .Ipaddr }}"
|
|
||||||
root="persistent"
|
|
||||||
echo "* Persistent install"
|
|
||||||
{{- else }}
|
|
||||||
root="wwinit"
|
|
||||||
{{- end }}
|
|
||||||
wwinit_args="root=$root wwinit.uri=${wwinit_uri} init=/init"
|
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Downloading kernel image..."
|
echo "Downloading kernel image..."
|
||||||
linux $kernel wwid=${net_default_mac} {{.KernelArgs}} $net_args $wwinit_args $ignition_args
|
linux $kernel wwid=${net_default_mac} {{.KernelArgs}} $net_args $wwinit_args
|
||||||
if [ $? != 0 ]
|
if [ $? != 0 ]
|
||||||
then
|
then
|
||||||
echo "!!"
|
echo "!!"
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ echo
|
|||||||
echo Downloading dracut initramfs...
|
echo Downloading dracut initramfs...
|
||||||
initrd --name initramfs ${uri}&stage=initramfs || goto error_reboot
|
initrd --name initramfs ${uri}&stage=initramfs || goto error_reboot
|
||||||
set dracut_net rd.neednet=1 {{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} ip={{$netdev.Device}}:dhcp {{end}}{{end}}
|
set dracut_net rd.neednet=1 {{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} ip={{$netdev.Device}}:dhcp {{end}}{{end}}
|
||||||
set dracut_wwinit root=wwinit wwinit.uri=${baseuri} init=/init
|
set dracut_wwinit root=wwinit:{{default "tmpfs" .Root}} wwinit.uri=${baseuri} init=/warewulf/run-init
|
||||||
goto boot_two_stage_dracut
|
goto boot_two_stage_dracut
|
||||||
|
|
||||||
:boot_single_stage
|
:boot_single_stage
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -12,6 +12,7 @@ require (
|
|||||||
github.com/containers/image/v5 v5.32.2
|
github.com/containers/image/v5 v5.32.2
|
||||||
github.com/containers/storage v1.57.1
|
github.com/containers/storage v1.57.1
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0
|
||||||
github.com/coreos/ignition/v2 v2.20.0
|
github.com/coreos/ignition/v2 v2.20.0
|
||||||
github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687
|
github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687
|
||||||
github.com/creasty/defaults v1.8.0
|
github.com/creasty/defaults v1.8.0
|
||||||
@@ -58,7 +59,6 @@ require (
|
|||||||
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
|
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
|
||||||
github.com/containers/ocicrypt v1.2.0 // indirect
|
github.com/containers/ocicrypt v1.2.0 // indirect
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||||
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f // indirect
|
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
|
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
|
||||||
|
|||||||
@@ -194,3 +194,16 @@ func (config *NodesYaml) FindDiscoverableNode() (Node, string, error) {
|
|||||||
|
|
||||||
return EmptyNode(), "", ErrNoUnconfigured
|
return EmptyNode(), "", ErrNoUnconfigured
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (node *Node) SetIds(id string) {
|
||||||
|
node.id = id
|
||||||
|
for diskId, disk := range node.Disks {
|
||||||
|
disk.id = diskId
|
||||||
|
for partitionId, partition := range disk.Partitions {
|
||||||
|
partition.id = partitionId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for fsId, fs := range node.FileSystems {
|
||||||
|
fs.id = fsId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ type NetDev struct {
|
|||||||
Holds the disks of a node
|
Holds the disks of a node
|
||||||
*/
|
*/
|
||||||
type Disk struct {
|
type Disk struct {
|
||||||
|
id string `yaml:"-" json:"-"`
|
||||||
WipeTable bool `yaml:"wipe_table,omitempty" json:"wipe_table,omitempty" lopt:"diskwipe" comment:"whether or not the partition tables shall be wiped"`
|
WipeTable bool `yaml:"wipe_table,omitempty" json:"wipe_table,omitempty" lopt:"diskwipe" comment:"whether or not the partition tables shall be wiped"`
|
||||||
Partitions map[string]*Partition `yaml:"partitions,omitempty" json:"partitions,omitempty"`
|
Partitions map[string]*Partition `yaml:"partitions,omitempty" json:"partitions,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -101,13 +102,14 @@ partition definition, the label must be uniq so its used as the key in the
|
|||||||
Partitions map
|
Partitions map
|
||||||
*/
|
*/
|
||||||
type Partition struct {
|
type Partition struct {
|
||||||
Number string `yaml:"number,omitempty" json:"number,omitempty" lopt:"partnumber" comment:"set the partition number, if not set next free slot is used" type:"uint"`
|
id string `yaml:"-" json:"-"`
|
||||||
SizeMiB string `yaml:"size_mib,omitempty" json:"size_mib,omitempty" lopt:"partsize" comment:"set the size of the partition, if not set maximal possible size is used" type:"uint"`
|
Number string `yaml:"number,omitempty" json:"number,omitempty" lopt:"partnumber" comment:"Set the partition number, if not set next free slot is used" type:"uint"`
|
||||||
|
SizeMiB string `yaml:"size_mib,omitempty" json:"size_mib,omitempty" lopt:"partsize" comment:"Set the size of the partition, if not set maximal possible size is used" type:"uint"`
|
||||||
StartMiB string `yaml:"start_mib,omitempty" json:"start_mib,omitempty" comment:"the start of the partition" type:"uint"`
|
StartMiB string `yaml:"start_mib,omitempty" json:"start_mib,omitempty" comment:"the start of the partition" type:"uint"`
|
||||||
TypeGuid string `yaml:"type_guid,omitempty" json:"type_guid,omitempty" comment:"Linux filesystem data will be used if empty"`
|
TypeGuid string `yaml:"type_guid,omitempty" json:"type_guid,omitempty" lopt:"parttype" comment:"Set the partition type GUID"`
|
||||||
Guid string `yaml:"guid,omitempty" json:"guid,omitempty" comment:"the GPT unique partition GUID"`
|
Guid string `yaml:"guid,omitempty" json:"guid,omitempty" comment:"the GPT unique partition GUID"`
|
||||||
WipePartitionEntry bool `yaml:"wipe_partition_entry,omitempty" json:"wipe_partition_entry,omitempty" comment:"if true, Ignition will clobber an existing partition if it does not match the config"`
|
WipePartitionEntry bool `yaml:"wipe_partition_entry,omitempty" json:"wipe_partition_entry,omitempty" comment:"if true, Ignition will clobber an existing partition if it does not match the config"`
|
||||||
ShouldExist bool `yaml:"should_exist,omitempty" json:"should_exist,omitempty" lopt:"partcreate" comment:"create partition if not exist"`
|
ShouldExist bool `yaml:"should_exist,omitempty" json:"should_exist,omitempty" lopt:"partcreate" comment:"Create partition if it does not exist"`
|
||||||
Resize bool `yaml:"resize,omitempty" json:"resize,omitempty" comment:"whether or not the existing partition should be resize"`
|
Resize bool `yaml:"resize,omitempty" json:"resize,omitempty" comment:"whether or not the existing partition should be resize"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +117,7 @@ type Partition struct {
|
|||||||
Definition of a filesystem. The device is uniq so its used as key
|
Definition of a filesystem. The device is uniq so its used as key
|
||||||
*/
|
*/
|
||||||
type FileSystem struct {
|
type FileSystem struct {
|
||||||
|
id string `yaml:"-" json:"-"`
|
||||||
Format string `yaml:"format,omitempty" json:"format,omitempty" lopt:"fsformat" comment:"format of the file system"`
|
Format string `yaml:"format,omitempty" json:"format,omitempty" lopt:"fsformat" comment:"format of the file system"`
|
||||||
Path string `yaml:"path,omitempty" json:"path,omitempty" lopt:"fspath" comment:"the mount point of the file system"`
|
Path string `yaml:"path,omitempty" json:"path,omitempty" lopt:"fspath" comment:"the mount point of the file system"`
|
||||||
WipeFileSystem bool `yaml:"wipe_filesystem,omitempty" json:"wipe_filesystem,omitempty" lopt:"fswipe" comment:"wipe file system at boot"`
|
WipeFileSystem bool `yaml:"wipe_filesystem,omitempty" json:"wipe_filesystem,omitempty" lopt:"fswipe" comment:"wipe file system at boot"`
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ func (config *NodesYaml) MergeNode(id string) (node Node, fields fieldMap, err e
|
|||||||
delete(fields, "Profiles")
|
delete(fields, "Profiles")
|
||||||
}
|
}
|
||||||
|
|
||||||
node.id = id
|
node.SetIds(id)
|
||||||
node.valid = true
|
node.valid = true
|
||||||
node.updatePrimaryNetDev()
|
node.updatePrimaryNetDev()
|
||||||
node.Profile.cleanLists()
|
node.Profile.cleanLists()
|
||||||
|
|||||||
@@ -10,10 +10,97 @@ import (
|
|||||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (node *Node) DiskList() (disks []*Disk) {
|
||||||
|
names := make([]string, 0, len(node.Disks))
|
||||||
|
for name := range node.Disks {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for _, name := range names {
|
||||||
|
disk := node.Disks[name]
|
||||||
|
if disk != nil {
|
||||||
|
disks = append(disks, disk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return disks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *Node) FileSystemList() (fs []*FileSystem) {
|
||||||
|
names := make([]string, 0, len(node.FileSystems))
|
||||||
|
for name := range node.FileSystems {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for _, name := range names {
|
||||||
|
fsys := node.FileSystems[name]
|
||||||
|
if fsys != nil {
|
||||||
|
fs = append(fs, fsys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disk *Disk) Id() string {
|
||||||
|
return disk.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disk *Disk) PartitionList() (partitions []*Partition) {
|
||||||
|
names := make([]string, 0, len(disk.Partitions))
|
||||||
|
for name := range disk.Partitions {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.SliceStable(names, func(i, j int) bool {
|
||||||
|
pi := disk.Partitions[names[i]]
|
||||||
|
pj := disk.Partitions[names[j]]
|
||||||
|
|
||||||
|
// Try to compare by Number (as int)
|
||||||
|
ni, erri := strconv.Atoi(pi.Number)
|
||||||
|
nj, errj := strconv.Atoi(pj.Number)
|
||||||
|
if erri == nil && errj == nil {
|
||||||
|
if ni != nj {
|
||||||
|
return ni < nj
|
||||||
|
}
|
||||||
|
} else if erri == nil {
|
||||||
|
return true // i has a number, j does not
|
||||||
|
} else if errj == nil {
|
||||||
|
return false // j has a number, i does not
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, compare by id if set
|
||||||
|
if pi.id != "" && pj.id != "" {
|
||||||
|
if pi.id != pj.id {
|
||||||
|
return pi.id < pj.id
|
||||||
|
}
|
||||||
|
} else if pi.id != "" {
|
||||||
|
return true // i has id, j does not
|
||||||
|
} else if pj.id != "" {
|
||||||
|
return false // j has id, i does not
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: compare by name (map key)
|
||||||
|
return names[i] < names[j]
|
||||||
|
})
|
||||||
|
for _, name := range names {
|
||||||
|
part := disk.Partitions[name]
|
||||||
|
if part != nil {
|
||||||
|
partitions = append(partitions, part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return partitions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (partition *Partition) Id() string {
|
||||||
|
return partition.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) Id() string {
|
||||||
|
return fs.id
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Create a ignition struct class for ignition
|
Create a ignition struct class for ignition
|
||||||
*/
|
*/
|
||||||
func (node *Node) GetStorage() (stor types_3_4.Storage, rep string, err error) {
|
func (node *Node) GetIgnitionStorage() (stor types_3_4.Storage, rep string, err error) {
|
||||||
var fileSystems []types_3_4.Filesystem
|
var fileSystems []types_3_4.Filesystem
|
||||||
for fsdevice, fs := range node.FileSystems {
|
for fsdevice, fs := range node.FileSystems {
|
||||||
var mountOptions []types_3_4.MountOption
|
var mountOptions []types_3_4.MountOption
|
||||||
@@ -141,8 +228,8 @@ type SimpleIgnitionConfig struct {
|
|||||||
/*
|
/*
|
||||||
Get a simple config which can be marshalled to json
|
Get a simple config which can be marshalled to json
|
||||||
*/
|
*/
|
||||||
func (node *Node) GetConfig() (conf SimpleIgnitionConfig, rep string, err error) {
|
func (node *Node) GetIgnitionConfig() (conf SimpleIgnitionConfig, rep string, err error) {
|
||||||
conf.Storage, rep, err = node.GetStorage()
|
conf.Storage, rep, err = node.GetIgnitionStorage()
|
||||||
conf.Ignition.Version = "3.1.0"
|
conf.Ignition.Version = "3.1.0"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
|
||||||
"github.com/warewulf/warewulf/internal/pkg/kernel"
|
"github.com/warewulf/warewulf/internal/pkg/kernel"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||||
)
|
)
|
||||||
@@ -39,7 +38,6 @@ type TemplateStruct struct {
|
|||||||
Tftp warewulfconf.TFTPConf
|
Tftp warewulfconf.TFTPConf
|
||||||
Paths warewulfconf.BuildConfig
|
Paths warewulfconf.BuildConfig
|
||||||
AllNodes []node.Node
|
AllNodes []node.Node
|
||||||
ImageList []string
|
|
||||||
node.Node
|
node.Node
|
||||||
// backward compatiblity
|
// backward compatiblity
|
||||||
Container string
|
Container string
|
||||||
@@ -50,7 +48,8 @@ type TemplateStruct struct {
|
|||||||
/*
|
/*
|
||||||
Initialize an TemplateStruct with the given node.NodeInfo
|
Initialize an TemplateStruct with the given node.NodeInfo
|
||||||
*/
|
*/
|
||||||
func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (tstruct TemplateStruct, err error) {
|
func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (TemplateStruct, error) {
|
||||||
|
var tstruct TemplateStruct
|
||||||
tstruct.Overlay = overlayName
|
tstruct.Overlay = overlayName
|
||||||
hostname, _ := os.Hostname()
|
hostname, _ := os.Hostname()
|
||||||
tstruct.BuildHost = hostname
|
tstruct.BuildHost = hostname
|
||||||
@@ -77,10 +76,6 @@ func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (t
|
|||||||
// init some convenience vars
|
// init some convenience vars
|
||||||
tstruct.Id = nodeData.Id()
|
tstruct.Id = nodeData.Id()
|
||||||
tstruct.Hostname = nodeData.Id()
|
tstruct.Hostname = nodeData.Id()
|
||||||
tstruct.ImageList, err = image.ListSources()
|
|
||||||
if err != nil {
|
|
||||||
return tstruct, err
|
|
||||||
}
|
|
||||||
tstruct.Container = nodeData.ImageName
|
tstruct.Container = nodeData.ImageName
|
||||||
tstruct.ContainerName = nodeData.ImageName
|
tstruct.ContainerName = nodeData.ImageName
|
||||||
// Backwards compatibility for templates using "Keys"
|
// Backwards compatibility for templates using "Keys"
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ func templateImageFileInclude(imagename string, filepath string) string {
|
|||||||
// Don't return an error as we use this function for template evaluation, so
|
// Don't return an error as we use this function for template evaluation, so
|
||||||
// error will turn up there as the return string
|
// error will turn up there as the return string
|
||||||
func createIgnitionJson(node *node.Node) string {
|
func createIgnitionJson(node *node.Node) string {
|
||||||
conf, rep, err := node.GetConfig()
|
conf, rep, err := node.GetIgnitionConfig()
|
||||||
if len(conf.Storage.Disks) == 0 && len(conf.Storage.Filesystems) == 0 {
|
if len(conf.Storage.Disks) == 0 && len(conf.Storage.Filesystems) == 0 {
|
||||||
wwlog.Debug("no disks or filesystems present, don't create a json object")
|
wwlog.Debug("no disks or filesystems present, don't create a json object")
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/Masterminds/sprig/v3"
|
"github.com/Masterminds/sprig/v3"
|
||||||
|
"github.com/coreos/go-systemd/v22/unit"
|
||||||
|
|
||||||
"github.com/warewulf/warewulf/internal/pkg/config"
|
"github.com/warewulf/warewulf/internal/pkg/config"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||||
@@ -496,12 +497,7 @@ func RenderTemplateFile(fileName string, data TemplateStruct) (
|
|||||||
"softlink": softlink,
|
"softlink": softlink,
|
||||||
"readlink": filepath.EvalSymlinks,
|
"readlink": filepath.EvalSymlinks,
|
||||||
"IgnitionJson": func() string {
|
"IgnitionJson": func() string {
|
||||||
str := createIgnitionJson(data.ThisNode)
|
return createIgnitionJson(data.ThisNode)
|
||||||
if str != "" {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
writeFile = false
|
|
||||||
return ""
|
|
||||||
},
|
},
|
||||||
"abort": func() string {
|
"abort": func() string {
|
||||||
wwlog.Debug("abort file called in %s", fileName)
|
wwlog.Debug("abort file called in %s", fileName)
|
||||||
@@ -513,7 +509,9 @@ func RenderTemplateFile(fileName string, data TemplateStruct) (
|
|||||||
backupFile = false
|
backupFile = false
|
||||||
return ""
|
return ""
|
||||||
},
|
},
|
||||||
"UniqueField": UniqueField,
|
"UniqueField": UniqueField,
|
||||||
|
"SystemdEscape": unit.UnitNameEscape,
|
||||||
|
"SystemdEscapePath": unit.UnitNamePathEscape,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge sprig.FuncMap with our FuncMap
|
// Merge sprig.FuncMap with our FuncMap
|
||||||
|
|||||||
@@ -11,66 +11,36 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Test_ignitionOverlay(t *testing.T) {
|
func Test_ignitionOverlay(t *testing.T) {
|
||||||
env := testenv.New(t)
|
tests := map[string]struct {
|
||||||
defer env.RemoveAll()
|
args []string
|
||||||
env.ImportFile("etc/warewulf/nodes.conf", "nodes.conf")
|
nodesConf string
|
||||||
env.ImportFile("var/lib/warewulf/overlays/ignition/rootfs/etc/systemd/system/ww4-disks.target.ww", "../rootfs/etc/systemd/system/ww4-disks.target.ww")
|
output string
|
||||||
env.ImportFile("var/lib/warewulf/overlays/ignition/rootfs/etc/systemd/system/ww4-mounts.ww", "../rootfs/etc/systemd/system/ww4-mounts.ww")
|
isJson bool
|
||||||
env.ImportFile("var/lib/warewulf/overlays/ignition/rootfs/warewulf/ignition.json.ww", "../rootfs/warewulf/ignition.json.ww")
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
log string
|
|
||||||
json bool
|
|
||||||
}{
|
}{
|
||||||
{
|
"ignition:ww4-disks.target": {
|
||||||
name: "ignition:ww4-disks.target",
|
args: []string{"--quiet", "--render", "node1", "ignition", "etc/systemd/system/ww4-disks.target.ww"},
|
||||||
args: []string{"--render", "node1", "ignition", "etc/systemd/system/ww4-disks.target.ww"},
|
nodesConf: `
|
||||||
log: ignition_disks,
|
nodes:
|
||||||
json: false,
|
node1:
|
||||||
},
|
disks:
|
||||||
{
|
/dev/vda:
|
||||||
name: "ignition:ww4-mounts",
|
wipe_table: true
|
||||||
args: []string{"--render", "node1", "ignition", "etc/systemd/system/ww4-mounts.ww"},
|
partitions:
|
||||||
log: ignition_mounts,
|
scratch:
|
||||||
json: false,
|
should_exist: true
|
||||||
},
|
swap:
|
||||||
{
|
number: "1"
|
||||||
name: "ignition:ignition.json",
|
size_mib: "1024"
|
||||||
args: []string{"--quiet", "--render", "node1", "ignition", "warewulf/ignition.json.ww"},
|
filesystems:
|
||||||
log: ignition_json,
|
/dev/disk/by-partlabel/scratch:
|
||||||
json: true,
|
format: btrfs
|
||||||
},
|
path: /scratch
|
||||||
}
|
wipe_filesystem: true
|
||||||
|
/dev/disk/by-partlabel/swap:
|
||||||
|
format: swap
|
||||||
|
path: swap`,
|
||||||
|
output: `# This file is autogenerated by warewulf
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
cmd := show.GetCommand()
|
|
||||||
cmd.SetArgs(tt.args)
|
|
||||||
stdout := bytes.NewBufferString("")
|
|
||||||
stderr := bytes.NewBufferString("")
|
|
||||||
logbuf := bytes.NewBufferString("")
|
|
||||||
cmd.SetOut(stdout)
|
|
||||||
cmd.SetErr(stderr)
|
|
||||||
wwlog.SetLogWriter(logbuf)
|
|
||||||
err := cmd.Execute()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Empty(t, stdout.String())
|
|
||||||
assert.Empty(t, stderr.String())
|
|
||||||
if tt.json {
|
|
||||||
assert.JSONEq(t, tt.log, logbuf.String())
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, tt.log, logbuf.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ignition_disks string = `backupFile: true
|
|
||||||
writeFile: true
|
|
||||||
Filename: etc/systemd/system/ww4-disks.target
|
|
||||||
# This file is autogenerated by warewulf
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=mount ww4 disks
|
Description=mount ww4 disks
|
||||||
# make sure that the disks are available
|
# make sure that the disks are available
|
||||||
@@ -80,14 +50,76 @@ Requisite=ignition-ww4-disks.service
|
|||||||
# Get the mounts
|
# Get the mounts
|
||||||
Wants=scratch.mount
|
Wants=scratch.mount
|
||||||
Wants=dev-disk-by\x2dpartlabel-swap.swap
|
Wants=dev-disk-by\x2dpartlabel-swap.swap
|
||||||
`
|
`,
|
||||||
|
isJson: false,
|
||||||
|
},
|
||||||
|
"ignition:ww4-disks.target (resources)": {
|
||||||
|
args: []string{"--quiet", "--render", "node1", "ignition", "etc/systemd/system/ww4-disks.target.ww"},
|
||||||
|
nodesConf: `
|
||||||
|
nodes:
|
||||||
|
node1:
|
||||||
|
resources:
|
||||||
|
ignition:
|
||||||
|
storage:
|
||||||
|
disks:
|
||||||
|
- device: /dev/vda
|
||||||
|
wipeTable: true
|
||||||
|
partitions:
|
||||||
|
- label: scratch
|
||||||
|
shouldExist: true
|
||||||
|
- label: swap
|
||||||
|
number: 1
|
||||||
|
sizeMiB: 1024
|
||||||
|
filesystems:
|
||||||
|
- device: /dev/disk/by-partlabel/scratch
|
||||||
|
format: btrfs
|
||||||
|
path: /scratch
|
||||||
|
wipeFilesystem: true
|
||||||
|
- device: /dev/disk/by-partlabel/swap
|
||||||
|
format: swap
|
||||||
|
path: swap`,
|
||||||
|
output: `# This file is autogenerated by warewulf
|
||||||
|
|
||||||
const ignition_mounts string = `backupFile: true
|
[Unit]
|
||||||
|
Description=mount ww4 disks
|
||||||
|
# make sure that the disks are available
|
||||||
|
Requires=ignition-ww4-disks.service
|
||||||
|
After=ignition-ww4-disks.service
|
||||||
|
Requisite=ignition-ww4-disks.service
|
||||||
|
# Get the mounts
|
||||||
|
Wants=scratch.mount
|
||||||
|
Wants=dev-disk-by\x2dpartlabel-swap.swap
|
||||||
|
`,
|
||||||
|
isJson: false,
|
||||||
|
},
|
||||||
|
"ignition:ww4-mounts": {
|
||||||
|
args: []string{"--quiet=false", "--render", "node1", "ignition", "etc/systemd/system/ww4-mounts.ww"},
|
||||||
|
nodesConf: `
|
||||||
|
nodes:
|
||||||
|
node1:
|
||||||
|
disks:
|
||||||
|
/dev/vda:
|
||||||
|
wipe_table: true
|
||||||
|
partitions:
|
||||||
|
scratch:
|
||||||
|
should_exist: true
|
||||||
|
swap:
|
||||||
|
number: "1"
|
||||||
|
size_mib: "1024"
|
||||||
|
filesystems:
|
||||||
|
/dev/disk/by-partlabel/scratch:
|
||||||
|
format: btrfs
|
||||||
|
path: /scratch
|
||||||
|
wipe_filesystem: true
|
||||||
|
/dev/disk/by-partlabel/swap:
|
||||||
|
format: swap
|
||||||
|
path: swap`,
|
||||||
|
output: `backupFile: true
|
||||||
writeFile: true
|
writeFile: true
|
||||||
Filename: scratch.mount
|
Filename: scratch.mount
|
||||||
|
|
||||||
|
|
||||||
# This file is autogenerated by warewulf
|
# This file is autogenerated by warewulf
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
ConditionPathExists=/warewulf/ignition.json
|
ConditionPathExists=/warewulf/ignition.json
|
||||||
Before=local-fs.target
|
Before=local-fs.target
|
||||||
@@ -99,11 +131,11 @@ What=/dev/disk/by-partlabel/scratch
|
|||||||
Type=btrfs
|
Type=btrfs
|
||||||
[Install]
|
[Install]
|
||||||
RequiredBy=local-fs.target
|
RequiredBy=local-fs.target
|
||||||
|
|
||||||
backupFile: true
|
backupFile: true
|
||||||
writeFile: true
|
writeFile: true
|
||||||
Filename: dev-disk-by\x2dpartlabel-swap.swap
|
Filename: dev-disk-by\x2dpartlabel-swap.swap
|
||||||
# This file is autogenerated by warewulf
|
# This file is autogenerated by warewulf
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
ConditionPathExists=/warewulf/ignition.json
|
ConditionPathExists=/warewulf/ignition.json
|
||||||
Requires=ignition-ww4-disks.service
|
Requires=ignition-ww4-disks.service
|
||||||
@@ -113,9 +145,91 @@ Before=swap.target
|
|||||||
What=/dev/disk/by-partlabel/swap
|
What=/dev/disk/by-partlabel/swap
|
||||||
[Install]
|
[Install]
|
||||||
RequiredBy=swap.target
|
RequiredBy=swap.target
|
||||||
`
|
`,
|
||||||
|
isJson: false,
|
||||||
|
},
|
||||||
|
"ignition:ww4-mounts (resources)": {
|
||||||
|
args: []string{"--quiet=false", "--render", "node1", "ignition", "etc/systemd/system/ww4-mounts.ww"},
|
||||||
|
nodesConf: `
|
||||||
|
nodes:
|
||||||
|
node1:
|
||||||
|
resources:
|
||||||
|
ignition:
|
||||||
|
storage:
|
||||||
|
disks:
|
||||||
|
- device: /dev/vda
|
||||||
|
wipeTable: true
|
||||||
|
partitions:
|
||||||
|
scratch:
|
||||||
|
shouldExist: true
|
||||||
|
swap:
|
||||||
|
number: 1
|
||||||
|
sizeMiB: 1024
|
||||||
|
filesystems:
|
||||||
|
- device: /dev/disk/by-partlabel/scratch
|
||||||
|
format: btrfs
|
||||||
|
path: /scratch
|
||||||
|
wipeFilesystem: true
|
||||||
|
- device: /dev/disk/by-partlabel/swap
|
||||||
|
format: swap
|
||||||
|
path: swap`,
|
||||||
|
output: `backupFile: true
|
||||||
|
writeFile: true
|
||||||
|
Filename: scratch.mount
|
||||||
|
|
||||||
const ignition_json string = `{
|
# This file is autogenerated by warewulf
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
ConditionPathExists=/warewulf/ignition.json
|
||||||
|
Before=local-fs.target
|
||||||
|
Requires=ignition-ww4-disks.service
|
||||||
|
After=ignition-ww4-disks.service
|
||||||
|
[Mount]
|
||||||
|
Where=/scratch
|
||||||
|
What=/dev/disk/by-partlabel/scratch
|
||||||
|
Type=btrfs
|
||||||
|
[Install]
|
||||||
|
RequiredBy=local-fs.target
|
||||||
|
backupFile: true
|
||||||
|
writeFile: true
|
||||||
|
Filename: dev-disk-by\x2dpartlabel-swap.swap
|
||||||
|
# This file is autogenerated by warewulf
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
ConditionPathExists=/warewulf/ignition.json
|
||||||
|
Requires=ignition-ww4-disks.service
|
||||||
|
After=ignition-ww4-disks.service
|
||||||
|
Before=swap.target
|
||||||
|
[Swap]
|
||||||
|
What=/dev/disk/by-partlabel/swap
|
||||||
|
[Install]
|
||||||
|
RequiredBy=swap.target
|
||||||
|
`,
|
||||||
|
isJson: false,
|
||||||
|
},
|
||||||
|
"ignition:ignition.json": {
|
||||||
|
args: []string{"--quiet", "--render", "node1", "ignition", "warewulf/ignition.json.ww"},
|
||||||
|
nodesConf: `
|
||||||
|
nodes:
|
||||||
|
node1:
|
||||||
|
disks:
|
||||||
|
/dev/vda:
|
||||||
|
wipe_table: true
|
||||||
|
partitions:
|
||||||
|
scratch:
|
||||||
|
should_exist: true
|
||||||
|
swap:
|
||||||
|
number: "1"
|
||||||
|
size_mib: "1024"
|
||||||
|
filesystems:
|
||||||
|
/dev/disk/by-partlabel/scratch:
|
||||||
|
format: btrfs
|
||||||
|
path: /scratch
|
||||||
|
wipe_filesystem: true
|
||||||
|
/dev/disk/by-partlabel/swap:
|
||||||
|
format: swap
|
||||||
|
path: swap`,
|
||||||
|
output: `{
|
||||||
"ignition": {
|
"ignition": {
|
||||||
"version": "3.1.0"
|
"version": "3.1.0"
|
||||||
},
|
},
|
||||||
@@ -155,4 +269,85 @@ const ignition_json string = `{
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`
|
}`,
|
||||||
|
isJson: true,
|
||||||
|
},
|
||||||
|
"ignition:ignition.json (resources)": {
|
||||||
|
args: []string{"--quiet", "--render", "node1", "ignition", "warewulf/ignition.json.ww"},
|
||||||
|
nodesConf: `
|
||||||
|
nodes:
|
||||||
|
node1:
|
||||||
|
resources:
|
||||||
|
ignition:
|
||||||
|
storage:
|
||||||
|
disks:
|
||||||
|
- device: /dev/vda
|
||||||
|
partitions:
|
||||||
|
- label: rootfs
|
||||||
|
number: 1
|
||||||
|
shouldExist: true
|
||||||
|
wipePartitionEntry: false
|
||||||
|
wipeTable: true
|
||||||
|
filesystems:
|
||||||
|
- device: /dev/disk/by-partlabel/rootfs
|
||||||
|
format: ext4
|
||||||
|
path: /
|
||||||
|
wipeFilesystem: false`,
|
||||||
|
output: `{
|
||||||
|
"storage": {
|
||||||
|
"disks": [
|
||||||
|
{
|
||||||
|
"device": "/dev/vda",
|
||||||
|
"partitions": [
|
||||||
|
{
|
||||||
|
"label": "rootfs",
|
||||||
|
"number": 1,
|
||||||
|
"shouldExist": true,
|
||||||
|
"wipePartitionEntry": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"wipeTable": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filesystems": [
|
||||||
|
{
|
||||||
|
"device": "/dev/disk/by-partlabel/rootfs",
|
||||||
|
"format": "ext4",
|
||||||
|
"path": "/",
|
||||||
|
"wipeFilesystem": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
isJson: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tt := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
env := testenv.New(t)
|
||||||
|
defer env.RemoveAll()
|
||||||
|
env.ImportFile("var/lib/warewulf/overlays/ignition/rootfs/etc/systemd/system/ww4-disks.target.ww", "../rootfs/etc/systemd/system/ww4-disks.target.ww")
|
||||||
|
env.ImportFile("var/lib/warewulf/overlays/ignition/rootfs/etc/systemd/system/ww4-mounts.ww", "../rootfs/etc/systemd/system/ww4-mounts.ww")
|
||||||
|
env.ImportFile("var/lib/warewulf/overlays/ignition/rootfs/warewulf/ignition.json.ww", "../rootfs/warewulf/ignition.json.ww")
|
||||||
|
env.WriteFile("etc/warewulf/nodes.conf", tt.nodesConf)
|
||||||
|
cmd := show.GetCommand()
|
||||||
|
cmd.SetArgs(tt.args)
|
||||||
|
stdout := bytes.NewBufferString("")
|
||||||
|
stderr := bytes.NewBufferString("")
|
||||||
|
logbuf := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(stdout)
|
||||||
|
cmd.SetErr(stderr)
|
||||||
|
wwlog.SetLogWriter(logbuf)
|
||||||
|
err := cmd.Execute()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, stdout.String())
|
||||||
|
assert.Empty(t, stderr.String())
|
||||||
|
if tt.isJson {
|
||||||
|
assert.JSONEq(t, tt.output, logbuf.String())
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, tt.output, logbuf.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
nodes:
|
|
||||||
node1:
|
|
||||||
disks:
|
|
||||||
/dev/vda:
|
|
||||||
wipe_table: true
|
|
||||||
partitions:
|
|
||||||
scratch:
|
|
||||||
should_exist: true
|
|
||||||
swap:
|
|
||||||
number: "1"
|
|
||||||
size_mib: "1024"
|
|
||||||
filesystems:
|
|
||||||
/dev/disk/by-partlabel/scratch:
|
|
||||||
format: btrfs
|
|
||||||
path: /scratch
|
|
||||||
wipe_filesystem: true
|
|
||||||
/dev/disk/by-partlabel/swap:
|
|
||||||
format: swap
|
|
||||||
path: swap
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
# This file is autogenerated by warewulf
|
# This file is autogenerated by warewulf
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=mount ww4 disks
|
Description=mount ww4 disks
|
||||||
# make sure that the disks are available
|
# make sure that the disks are available
|
||||||
@@ -6,19 +7,20 @@ Requires=ignition-ww4-disks.service
|
|||||||
After=ignition-ww4-disks.service
|
After=ignition-ww4-disks.service
|
||||||
Requisite=ignition-ww4-disks.service
|
Requisite=ignition-ww4-disks.service
|
||||||
# Get the mounts
|
# Get the mounts
|
||||||
{{- range $fsdevice,$fs := .FileSystems }}
|
{{- if .FileSystems }}
|
||||||
{{- $prefix := $fsdevice }}
|
{{- range $fsdevice, $fs := .FileSystems }}
|
||||||
{{- $prefix = replace "-" "\\x2d" $fsdevice }}
|
{{- if eq $fs.Format "swap" }}
|
||||||
{{- $prefix = replace "/" "-" $prefix }}
|
Wants={{ $fsdevice | SystemdEscapePath }}.swap
|
||||||
{{- $prefix = substr 1 -1 $prefix }}{{ $suffix := "mount" }}
|
{{- else if ne $fs.Path "" }}
|
||||||
{{- if eq $fs.Format "swap"}}
|
Wants={{ $fs.Path | SystemdEscapePath }}.mount
|
||||||
{{- $prefix = replace "/dev/disk/by-partlabel/" "dev-disk-by\\x2dpartlabel-" $fsdevice }}{{ $suffix = "swap"}}
|
{{- end }}
|
||||||
{{- else }}
|
{{- end }}
|
||||||
{{- if ne $fs.Path "" }}
|
{{- else if .Resources.ignition.storage.filesystems }}
|
||||||
{{- $prefix = replace "-" "\\x2d" $fs.Path }}
|
{{- range $fs := .Resources.ignition.storage.filesystems }}
|
||||||
{{- $prefix = replace "/" "-" $prefix }}
|
{{- if eq $fs.format "swap" }}
|
||||||
{{- $prefix = substr 1 -1 $prefix }}
|
Wants={{ $fs.device | SystemdEscapePath }}.swap
|
||||||
|
{{- else if ne $fs.path "" }}
|
||||||
|
Wants={{ $fs.path | SystemdEscapePath }}.mount
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
Wants={{ print $prefix "." $suffix }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
{{- range $fsdevice,$fs := .FileSystems }}
|
{{- if .FileSystems }}
|
||||||
{{- $prefix := replace "-" "\\x2d" $fs.Path }}
|
{{- range $fsdevice, $fs := .FileSystems }}
|
||||||
{{- $prefix = replace "/" "-" $prefix }}
|
{{- if eq $fs.Format "swap" }}
|
||||||
{{- $prefix = substr 1 -1 $prefix }}
|
{{ file (print ($fsdevice | SystemdEscapePath) ".swap") }}
|
||||||
{{ $suffix := "mount"}}
|
|
||||||
{{- if $fs.Label }}{{ $prefix = $fs.Label }}{{ end }}
|
|
||||||
{{- if eq $fs.Format "swap" }}{{ $prefix = replace "/dev/disk/by-partlabel/" "dev-disk-by\\x2dpartlabel-" $fsdevice }}{{ $suffix = "swap" }}{{ end }}
|
|
||||||
{{ file (print $prefix "." $suffix) }}
|
|
||||||
# This file is autogenerated by warewulf
|
# This file is autogenerated by warewulf
|
||||||
{{- if eq $fs.Format "swap"}}
|
|
||||||
[Unit]
|
[Unit]
|
||||||
ConditionPathExists=/warewulf/ignition.json
|
ConditionPathExists=/warewulf/ignition.json
|
||||||
Requires=ignition-ww4-disks.service
|
Requires=ignition-ww4-disks.service
|
||||||
@@ -17,7 +13,10 @@ Before=swap.target
|
|||||||
What={{ $fsdevice }}
|
What={{ $fsdevice }}
|
||||||
[Install]
|
[Install]
|
||||||
RequiredBy=swap.target
|
RequiredBy=swap.target
|
||||||
{{- else }}
|
{{- else if ne $fs.Path "" }}
|
||||||
|
{{ file (print ($fs.Path | SystemdEscapePath) ".mount") }}
|
||||||
|
# This file is autogenerated by warewulf
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
ConditionPathExists=/warewulf/ignition.json
|
ConditionPathExists=/warewulf/ignition.json
|
||||||
Before=local-fs.target
|
Before=local-fs.target
|
||||||
@@ -28,9 +27,49 @@ Where={{ $fs.Path }}
|
|||||||
What={{ $fsdevice }}
|
What={{ $fsdevice }}
|
||||||
Type={{ $fs.Format }}
|
Type={{ $fs.Format }}
|
||||||
{{- if $fs.MountOptions }}
|
{{- if $fs.MountOptions }}
|
||||||
Options={{range $index,$opt := $fs.MountOptions }}{{if $index }},{{ end }}{{ $opt }}{{ end }}
|
Options={{ $fs.MountOptions | join "." }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
[Install]
|
[Install]
|
||||||
RequiredBy=local-fs.target
|
RequiredBy=local-fs.target
|
||||||
|
{{- else }}
|
||||||
|
{{- continue }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if .Resources.ignition.storage.filesystems }}
|
||||||
|
{{- range $fs := .Resources.ignition.storage.filesystems }}
|
||||||
|
{{- if eq $fs.format "swap" }}
|
||||||
|
{{ file (print ($fs.device | SystemdEscapePath) ".swap") }}
|
||||||
|
# This file is autogenerated by warewulf
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
ConditionPathExists=/warewulf/ignition.json
|
||||||
|
Requires=ignition-ww4-disks.service
|
||||||
|
After=ignition-ww4-disks.service
|
||||||
|
Before=swap.target
|
||||||
|
[Swap]
|
||||||
|
What={{ $fs.device }}
|
||||||
|
[Install]
|
||||||
|
RequiredBy=swap.target
|
||||||
|
{{- else if ne $fs.path "" }}
|
||||||
|
{{ file (print ($fs.path | SystemdEscapePath) ".mount") }}
|
||||||
|
# This file is autogenerated by warewulf
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
ConditionPathExists=/warewulf/ignition.json
|
||||||
|
Before=local-fs.target
|
||||||
|
Requires=ignition-ww4-disks.service
|
||||||
|
After=ignition-ww4-disks.service
|
||||||
|
[Mount]
|
||||||
|
Where={{ $fs.path }}
|
||||||
|
What={{ $fs.device }}
|
||||||
|
Type={{ $fs.format }}
|
||||||
|
{{- if $fs.mountOptions }}
|
||||||
|
Options={{ $fs.mountOptions | join "." }}
|
||||||
|
{{- end }}
|
||||||
|
[Install]
|
||||||
|
RequiredBy=local-fs.target
|
||||||
|
{{- else }}
|
||||||
|
{{- continue }}
|
||||||
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{{ IgnitionJson }}
|
{{ coalesce IgnitionJson (.Resources.ignition | toJson) }}
|
||||||
23
overlays/ignition/rootfs/warewulf/wwinit.d/10-ignition.sh
Normal file
23
overlays/ignition/rootfs/warewulf/wwinit.d/10-ignition.sh
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
|
||||||
|
|
||||||
|
if ! command -v info >/dev/null; then
|
||||||
|
info() {
|
||||||
|
printf '%s\n' "$*"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v die >/dev/null; then
|
||||||
|
die() {
|
||||||
|
printf '%s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v ignition >/dev/null; then :
|
||||||
|
info "warewulf: ignition: partition and format disks"
|
||||||
|
ignition --config-cache="${PREFIX}/warewulf/ignition.json" --platform=metal --stage=disks || die "warewulf: ignition: failed to partition/format disk"
|
||||||
|
else
|
||||||
|
info "warewulf: ignition not found"
|
||||||
|
fi
|
||||||
@@ -1 +0,0 @@
|
|||||||
{{ IgnitionJson }}
|
|
||||||
@@ -1,53 +1,65 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
echo "Warewulf pre-init (/init)"
|
if ! command -v info >/dev/null; then
|
||||||
echo
|
info() {
|
||||||
echo "Mounting kernel file systems..."
|
printf '%s\n' "$*"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v die >/dev/null; then
|
||||||
|
die() {
|
||||||
|
printf '%s\n' "$*" >&2
|
||||||
|
echo
|
||||||
|
echo "Rebooting in 1 minute..."
|
||||||
|
sleep 60
|
||||||
|
echo b > /proc/sysrq-trigger || /sbin/reboot -f
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "warewulf: running pre-init wwinit process..."
|
||||||
|
|
||||||
|
info "warewulf: mounting kernel file systems..."
|
||||||
mountpoint -q /proc || (mkdir -p /proc && mount -t proc proc /proc && echo "Mounted /proc")
|
mountpoint -q /proc || (mkdir -p /proc && mount -t proc proc /proc && echo "Mounted /proc")
|
||||||
mountpoint -q /dev || (mkdir -p /dev && mount -t devtmpfs devtmpfs /dev && echo "Mounted /dev")
|
mountpoint -q /dev || (mkdir -p /dev && mount -t devtmpfs devtmpfs /dev && echo "Mounted /dev")
|
||||||
mountpoint -q /sys || (mkdir -p /sys && mount -t sysfs sysfs /sys && echo "Mounted /sys")
|
mountpoint -q /sys || (mkdir -p /sys && mount -t sysfs sysfs /sys && echo "Mounted /sys")
|
||||||
# https://systemd.io/INITRD_INTERFACE/
|
# https://systemd.io/INITRD_INTERFACE/
|
||||||
mountpoint -q /run || (mkdir -p /run && mount -t tmpfs -o mode=0755,nodev,nosuid,strictatime tmpfs /run && echo "Mounted /run")
|
mountpoint -q /run || (mkdir -p /run && mount -t tmpfs -o mode=0755,nodev,nosuid,strictatime tmpfs /run && echo "Mounted /run")
|
||||||
echo
|
|
||||||
echo "Loading /warewulf/config..."
|
info "warewulf: loading /warewulf/config..."
|
||||||
if [ -f "/warewulf/config" ]; then
|
if [ -f "/warewulf/config" ]; then
|
||||||
. /warewulf/config
|
. /warewulf/config
|
||||||
else
|
else
|
||||||
echo "ERROR: Warewulf configuration file not found."
|
die "warewulf: ERROR: /warewulf/config not found"
|
||||||
echo "Rebooting in 1 minute..."
|
|
||||||
sleep 60
|
|
||||||
echo b > /proc/sysrq-trigger || /sbin/reboot -f
|
|
||||||
fi
|
fi
|
||||||
echo
|
|
||||||
echo "Configuring root file system..."
|
|
||||||
|
|
||||||
WWPRESCRIPTS=/warewulf/wwprescripts
|
if [ -x /warewulf/run-wwinit.d ]; then
|
||||||
chmod +rx "${WWPRESCRIPTS}"
|
/warewulf/run-wwinit.d
|
||||||
|
fi
|
||||||
|
|
||||||
|
NEXT_INIT=/warewulf/run-init
|
||||||
|
chmod +rx "${NEXT_INIT}"
|
||||||
|
|
||||||
|
info "warewulf: configuring root file system..."
|
||||||
WWROOT="${WWROOT:-initramfs}"
|
WWROOT="${WWROOT:-initramfs}"
|
||||||
if [ "${WWROOT}" = "initramfs" ]; then
|
if [ "${WWROOT}" = "initramfs" ]; then
|
||||||
echo "WWROOT=${WWROOT}: using initial rootfs and invoking ${WWPRESCRIPTS}..."
|
info "warewulf: using initial rootfs and invoking ${NEXT_INIT}..."
|
||||||
exec "${WWPRESCRIPTS}"
|
exec "${NEXT_INIT}"
|
||||||
elif [ "${WWROOT}" = "persistent" ] ; then
|
|
||||||
echo "WWROOT=${WWROOT}: booting from DISK"
|
|
||||||
exec "${WWPRESCRIPTS}"
|
|
||||||
elif [ "${WWROOT}" = "ramfs" -o "${WWROOT}" = "tmpfs" ]; then
|
|
||||||
echo "WWROOT=${WWROOT}: setting up new ${WWROOT} rootfs (/newroot)..."
|
|
||||||
mkdir /newroot
|
|
||||||
mount wwroot /newroot -t ${WWROOT} -o mpol=interleave # mpol ignored for ramfs
|
|
||||||
tar -cf - --exclude ./proc --exclude ./sys --exclude ./dev --exclude --exclude ./newroot . | tar -xf - -C /newroot
|
|
||||||
mkdir /newroot/proc /newroot/dev /newroot/sys 2>/dev/null
|
|
||||||
echo "Switching to new rootfs (/newroot) and invoking ${WWPRESCRIPTS}..."
|
|
||||||
exec /sbin/switch_root /newroot "${WWPRESCRIPTS}"
|
|
||||||
else
|
else
|
||||||
echo "ERROR: Unrecognized rootfs type requested: ${WWROOT}"
|
mkdir /newroot
|
||||||
echo "Rebooting in 1 minute..."
|
info "warewulf: mounting ${WWROOT} at /newroot"
|
||||||
sleep 60
|
if [ "${WWROOT}" = "ramfs" -o "${WWROOT}" = "tmpfs" ]; then
|
||||||
echo b > /proc/sysrq-trigger || /sbin/reboot -f
|
mount wwroot /newroot -t ${WWROOT} -o mpol=interleave # mpol ignored for ramfs
|
||||||
|
else
|
||||||
|
mount ${WWROOT} /newroot
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "warewulf: copying image to /newroot..."
|
||||||
|
tar -cf - --exclude ./proc --exclude ./sys --exclude ./dev --exclude --exclude ./newroot . | tar -xf - -C /newroot
|
||||||
|
|
||||||
|
mkdir /newroot/proc /newroot/dev /newroot/sys 2>/dev/null
|
||||||
|
|
||||||
|
info "warewulf: switching to /newroot and invoking ${NEXT_INIT}..."
|
||||||
|
exec /sbin/switch_root /newroot "${NEXT_INIT}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
die "warewulf: ERROR: wwinit encountered a problem."
|
||||||
echo "ERROR: wwinit encountered a problem."
|
|
||||||
echo "Rebooting in 1 minute..."
|
|
||||||
sleep 60
|
|
||||||
echo b > /proc/sysrq-trigger || /sbin/reboot -f
|
|
||||||
|
|||||||
40
overlays/wwinit/rootfs/warewulf/run-init
Executable file
40
overlays/wwinit/rootfs/warewulf/run-init
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if ! command -v info >/dev/null; then
|
||||||
|
info() {
|
||||||
|
printf '%s\n' "$*"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
. /warewulf/config
|
||||||
|
|
||||||
|
scriptdir=/warewulf/init.d
|
||||||
|
echo "warewulf: running scripts in ${scriptdir}..."
|
||||||
|
ls -1 "${scriptdir}/" | while read -r name; do
|
||||||
|
info "warewulf: ${name}"
|
||||||
|
sh "${scriptdir}/${name}"
|
||||||
|
done
|
||||||
|
|
||||||
|
init="${WWINIT}"
|
||||||
|
if [ -z "${init}" ]
|
||||||
|
then
|
||||||
|
for candidate in /sbin/init /etc/init /bin/init
|
||||||
|
do
|
||||||
|
if [ -x "${candidate}" ]
|
||||||
|
then
|
||||||
|
init="${candidate}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${init}" ]
|
||||||
|
then
|
||||||
|
info "warewulf: invoking ${init}..."
|
||||||
|
exec "${init}"
|
||||||
|
else
|
||||||
|
echo "ERROR: init not defined or not found."
|
||||||
|
echo "Rebooting in 1 minute..."
|
||||||
|
sleep 60
|
||||||
|
echo b > /proc/sysrq-trigger || /sbin/reboot -f
|
||||||
|
fi
|
||||||
18
overlays/wwinit/rootfs/warewulf/run-wwinit.d
Executable file
18
overlays/wwinit/rootfs/warewulf/run-wwinit.d
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if ! command -v info >/dev/null; then
|
||||||
|
info() {
|
||||||
|
printf '%s\n' "$*"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
scriptdir="${PREFIX}/warewulf/wwinit.d"
|
||||||
|
if [ -d "${scriptdir}" ]; then
|
||||||
|
info "warewulf: running scripts in ${scriptdir}..."
|
||||||
|
ls -1 "${scriptdir}/" | while read -r name; do
|
||||||
|
info "warewulf: ${name}"
|
||||||
|
PREFIX=$PREFIX sh "${scriptdir}/${name}"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
info "warewulf: ${scriptdir} not found"
|
||||||
|
fi
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#!/usr/bin/sh
|
|
||||||
|
|
||||||
. /warewulf/config
|
|
||||||
|
|
||||||
echo "Warewulf prescript runner (/warewulf/wwprescripts)"
|
|
||||||
scriptdir=/warewulf/init.d
|
|
||||||
echo "Looking for prescripts in /warewulf/init.d/..."
|
|
||||||
ls -1 "${scriptdir}/" | while read -r name; do
|
|
||||||
echo "Running prescript: ${name}..."
|
|
||||||
sh "${scriptdir}/${name}"
|
|
||||||
done
|
|
||||||
|
|
||||||
init="${WWINIT}"
|
|
||||||
if [ -z "${init}" ]
|
|
||||||
then
|
|
||||||
for candidate in /sbin/init /etc/init /bin/init
|
|
||||||
do
|
|
||||||
if [ -x "${candidate}" ]
|
|
||||||
then
|
|
||||||
init="${candidate}"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${init}" ]
|
|
||||||
then
|
|
||||||
echo
|
|
||||||
echo "Running ${init}..."
|
|
||||||
exec "${init}"
|
|
||||||
else
|
|
||||||
echo "ERROR: init not defined and not found."
|
|
||||||
echo "Rebooting in 1 minute..."
|
|
||||||
sleep 60
|
|
||||||
echo b > /proc/sysrq-trigger || /sbin/reboot -f
|
|
||||||
fi
|
|
||||||
1
overlays/wwinit/rootfs/warewulf/wwprescripts
Symbolic link
1
overlays/wwinit/rootfs/warewulf/wwprescripts
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
run-init
|
||||||
@@ -192,8 +192,15 @@ In particular:
|
|||||||
- Update PAM configuration to allow missing shadow entries
|
- Update PAM configuration to allow missing shadow entries
|
||||||
- Relabel the file system for SELinux
|
- Relabel the file system for SELinux
|
||||||
|
|
||||||
Other overlays may place additional scripts in ``/warewulf/init.d/`` to affect
|
Other overlays can place scripts in one of two locations for additional pre-init
|
||||||
node configuration in this pre-boot environment.
|
provisioning actions:
|
||||||
|
|
||||||
|
- **/warewulf/wwinit.d/:** executed in the initial root final system before the
|
||||||
|
image is loaded into its final location. In a two-stage boot, these scripts
|
||||||
|
are executed in the Dracut initramfs.
|
||||||
|
|
||||||
|
- **/warewulf/init.d/:** executed in the final root file system but before
|
||||||
|
calling ``init``.
|
||||||
|
|
||||||
wwclient
|
wwclient
|
||||||
--------
|
--------
|
||||||
@@ -309,6 +316,36 @@ ignition
|
|||||||
--------
|
--------
|
||||||
|
|
||||||
The **ignition** overlay defines partitions and file systems on local disks.
|
The **ignition** overlay defines partitions and file systems on local disks.
|
||||||
|
Configuration may be provided via native disk, partition, and filesystem fields
|
||||||
|
or via an ``ignition`` resource.
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
ignition:
|
||||||
|
storage:
|
||||||
|
disks:
|
||||||
|
- device: /dev/vda
|
||||||
|
partitions:
|
||||||
|
- label: scratch
|
||||||
|
shouldExist: true
|
||||||
|
wipePartitionEntry: true
|
||||||
|
wipeTable: true
|
||||||
|
filesystems:
|
||||||
|
- device: /dev/disk/by-partlabel/scratch
|
||||||
|
format: btrfs
|
||||||
|
path: /scratch
|
||||||
|
wipeFilesystem: false
|
||||||
|
|
||||||
|
If any disk/partition/filesystem configuration is provided for a node with
|
||||||
|
explicit arguments to ``wwctl <node|profile> set``, the ``ignition`` resource is
|
||||||
|
ignored.
|
||||||
|
|
||||||
|
To use ignition during Dracut (so that the root file system may be provisioned
|
||||||
|
before the image is loaded) include Ignition in the Dracut image.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
wwctl image exec rockylinux-9 -- /usr/bin/dracut --force --no-hostonly --add wwinit --add ignition --regenerate-all
|
||||||
|
|
||||||
debug
|
debug
|
||||||
-----
|
-----
|
||||||
|
|||||||
@@ -148,7 +148,13 @@ symlink.
|
|||||||
IgnitionJson
|
IgnitionJson
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Generates JSON suitable for use by Ignition to create
|
Generates JSON suitable for use by Ignition to create partitions and file
|
||||||
|
systems, from the data stored in a node's native ``disks`` and ``filesystems``
|
||||||
|
fields.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
{{ IgnitionJson }}
|
||||||
|
|
||||||
abort
|
abort
|
||||||
-----
|
-----
|
||||||
@@ -174,10 +180,9 @@ nobackup
|
|||||||
UniqueField
|
UniqueField
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
UniqueField returns a filtered version of a multi-line input string. input is
|
Returns a filtered version of a multi-line input string. input is expected to be
|
||||||
expected to be a field-separated format with one record per line (terminated by
|
a field-separated format with one record per line (terminated by `\n`). Order of
|
||||||
`\n`). Order of lines is preserved, with the first matching line taking
|
lines is preserved, with the first matching line taking precedence.
|
||||||
precedence.
|
|
||||||
|
|
||||||
For example, the following template snippet has been used in the ``syncuser`` overlay
|
For example, the following template snippet has been used in the ``syncuser`` overlay
|
||||||
to generate a combined ``/etc/passwd``.
|
to generate a combined ``/etc/passwd``.
|
||||||
@@ -191,6 +196,24 @@ to generate a combined ``/etc/passwd``.
|
|||||||
| UniqueField ":" 0 | trim
|
| UniqueField ":" 0 | trim
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
SystemdEscape
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Escapes a string for use in a systemd unit file.
|
||||||
|
|
||||||
|
Escape rules are documented at `systemd.unit. <https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#String%20Escaping%20for%20Inclusion%20in%20Unit%20Names>`_
|
||||||
|
|
||||||
|
SystemdEscapePath
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Escapes a path for use in a systemd unit file.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
{{ file (print ($fs.path | SystemdEscapePath) ".mount") }}
|
||||||
|
|
||||||
|
Escape rules are documented at `systemd.unit. <https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#String%20Escaping%20for%20Inclusion%20in%20Unit%20Names>`_
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
========
|
========
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ For example, the ``imgextract`` command can be `explicitly enabled`_.
|
|||||||
support for ZLIB and GZIP archive image formats.
|
support for ZLIB and GZIP archive image formats.
|
||||||
|
|
||||||
Configuring Warewulf (>= v4.5.0)
|
Configuring Warewulf (>= v4.5.0)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
In Warewulf v4.5.0, Warewulf can be configured to use these files using the
|
In Warewulf v4.5.0, Warewulf can be configured to use these files using the
|
||||||
``tftp.ipxe`` and ``paths.ipxesource`` configuration parameters in
|
``tftp.ipxe`` and ``paths.ipxesource`` configuration parameters in
|
||||||
@@ -321,8 +321,8 @@ directly from the node's assigned image.
|
|||||||
|
|
||||||
.. _booting with dracut:
|
.. _booting with dracut:
|
||||||
|
|
||||||
Booting with dracut
|
Two-stage boot: dracut
|
||||||
===================
|
======================
|
||||||
|
|
||||||
Some systems, typically due to limitations in their BIOS or EFI firmware, are
|
Some systems, typically due to limitations in their BIOS or EFI firmware, are
|
||||||
unable to load image of a certain size directly with a traditional bootloader,
|
unable to load image of a certain size directly with a traditional bootloader,
|
||||||
@@ -337,21 +337,15 @@ must be installed in the image.
|
|||||||
With the ``warewulf-dracut`` package installed in the image, you can then build
|
With the ``warewulf-dracut`` package installed in the image, you can then build
|
||||||
an initramfs inside the image.
|
an initramfs inside the image.
|
||||||
|
|
||||||
EL installation
|
|
||||||
---------------
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
|
# Enterprise Linux
|
||||||
wwctl image exec rockylinux-9 --build=false -- /usr/bin/dnf -y install https://github.com/warewulf/warewulf/releases/download/v4.6.1/warewulf-dracut-4.6.1-1.el9.noarch.rpm
|
wwctl image exec rockylinux-9 --build=false -- /usr/bin/dnf -y install https://github.com/warewulf/warewulf/releases/download/v4.6.1/warewulf-dracut-4.6.1-1.el9.noarch.rpm
|
||||||
wwctl image exec rockylinux-9 -- /usr/bin/dracut --force --no-hostonly --add wwinit --regenerate-all
|
wwctl image exec rockylinux-9 -- /usr/bin/dracut --force --no-hostonly --add wwinit --regenerate-all
|
||||||
|
|
||||||
|
# SUSE
|
||||||
SUSE installation
|
wwctl image exec leap-15 --build=false -- /usr/bin/zypper -y install https://github.com/warewulf/warewulf/releases/download/v4.6.1/warewulf-dracut-4.6.1-1.suse.lp155.noarch.rpm
|
||||||
-----------------
|
wwctl image exec leap-15 -- /usr/bin/dracut --force --no-hostonly --add wwinit --regenerate-all
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
zypper -y install warewulf-dracut
|
|
||||||
dracut --force --no-hostonly --add wwinit --regenerate-all
|
|
||||||
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@@ -403,42 +397,70 @@ to 50% of physical memory. This size limit may be adjusted using the kernel
|
|||||||
argument `wwinit.tmpfs.size`. (This parameter is passed to the `size` option
|
argument `wwinit.tmpfs.size`. (This parameter is passed to the `size` option
|
||||||
during tmpfs mount. See ``tmpfs(5)`` for more details.)
|
during tmpfs mount. See ``tmpfs(5)`` for more details.)
|
||||||
|
|
||||||
Persistent installation
|
.. _provision to disk:
|
||||||
========================
|
|
||||||
With the `dracut` installation enabled warewulf can also install
|
|
||||||
the node image to a harddrive. On the first boot of the node the
|
|
||||||
compressed node image is simply dumped onto the configured partition.
|
|
||||||
|
|
||||||
|
Provision to disk
|
||||||
|
=================
|
||||||
|
|
||||||
|
*New in Warewulf v4.6.2*
|
||||||
|
|
||||||
|
As a tech preview, the Warewulf two-stage boot process can provision the node
|
||||||
|
image to local storage.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
warewulf doesn't install the bootloader to the disk and add UEFI
|
|
||||||
entries. In order to boot the node network booting is required and
|
|
||||||
at every boot the kernel and the initrd is transfered over the network.
|
|
||||||
|
|
||||||
Configuration
|
This functionality is a technology preview and should be used with care. Pay
|
||||||
-------------
|
specific attention to ``wipeFilesytem`` (ignition) and ``overwrite`` (mkfs)
|
||||||
|
settings.
|
||||||
The node image will be installed to the partition called `rootfs`. You
|
|
||||||
can add add a rootfs with e.g. following command
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
wwctl node set n01 \
|
|
||||||
--diskname /dev/vda --diskwipe \
|
|
||||||
--partname rootfs --partcreate --partnumber 1 \
|
|
||||||
--fsname rootfs --fsformat ext4 --fspath /rootfs
|
|
||||||
|
|
||||||
Now the node boot method has to be set to persitent with following command
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
wwctl node set n01 --root persistent
|
|
||||||
|
|
||||||
and the node rebooted.
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If the boot mode is perisiten the configured partion labeled `rootfs` will
|
Warewulf doesn't install a bootloader to the disk or add UEFI entries. Nodes
|
||||||
be mounted as `/`. With any other boot method mount point confiured (here `/roofs`)
|
still request an image and configuration from the Warewulf server on every
|
||||||
will be used.
|
boot.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
While provisioning to disk should be possible during a single-stage boot, not
|
||||||
|
all features are available:
|
||||||
|
|
||||||
|
- Warewulf does not perform hardware detection to ensure that necessary
|
||||||
|
kernel modules are loaded prior to init.
|
||||||
|
- Warewulf does not load udev to ensure that ``/dev/disk/by-*`` symlinks are
|
||||||
|
available prior to init.
|
||||||
|
|
||||||
|
With Ignition
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Warewulf needs a prepared file system to deploy the image to. Warewulf can
|
||||||
|
provision this file system using Ignition. To use Ignition, include ``ignition``
|
||||||
|
in your system overlay. The ignition overlay provisions disks during init and,
|
||||||
|
optionally, during the first stage of a two-stage boot. This allows the
|
||||||
|
root file system to be provisioned before the image is loaded.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
wwctl node set wwnode1 \
|
||||||
|
--diskname /dev/vda --diskwipe \
|
||||||
|
--partname rootfs --partcreate --partnumber 1 \
|
||||||
|
--fsname rootfs --fsformat ext4 --fspath /
|
||||||
|
|
||||||
|
In order to allow Dracut to provision the disk, partition, and file system,
|
||||||
|
Ignition must be included in the Dracut image.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
wwctl image exec rockylinux-9 -- /usr/bin/dracut --force --no-hostonly --add wwinit --add ignition --regenerate-all
|
||||||
|
|
||||||
|
The necessary file system may alternatively be prepared out-of-band.
|
||||||
|
|
||||||
|
|
||||||
|
Configuring the root device
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Set the desired storage device for the node image using the ``--root``
|
||||||
|
parameter.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
wwctl node set wwnode1 --root /dev/disk/by-partlabel/rootfs
|
||||||
|
|||||||
Reference in New Issue
Block a user