diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..886e7304 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" + target-branch: development diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 38d73851..fe0741e8 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -3,10 +3,19 @@ name: publish_docs on: push: + branches: + - main + - development + pull_request: + branches: + - main + - development + paths: + - 'userdocs/**' jobs: publish: - if: ${{ github.repository_owner == 'hcpng' }} + if: ${{ github.repository_owner == 'hpcng' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -34,6 +43,7 @@ jobs: make html - name: Install SSH key + if: github.event_name != 'pull_request' env: SSH_AUTH_SOCK: /tmp/ssh_agent.sock run: | @@ -50,6 +60,7 @@ jobs: git config --global user.name "gh-actions" - name: Update website repo + if: github.event_name != 'pull_request' run: | git clone git@github.com:hpcng/warewulf-web.git ~/warewulf-web mkdir -p ~/warewulf-web/static/docs diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 255be759..53901d02 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -2,7 +2,13 @@ on: push: branches: - main + - development pull_request: + branches: + - main + - development + paths-ignore: + - 'docs/**' name: golangci-lint diff --git a/CHANGELOG.md b/CHANGELOG.md index a8dbdb79..c5245f88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [4..4.0] 2023-01-18 +## Unreleased + +### Fixed + +- The correct header is now displayed when `-al` flags are specified to overlay + list. + +### Changed + +- The primary hostname and warewulf server fqdn are now the canonical name in + `/etc/hosts` + +## [4.4.0] 2023-01-18 ### Added @@ -20,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a typo in a log message. #631 - Boolean attributes now correctly account for profile and default values. #630 -- Kernvel version is shown correctly +- Kernel version is shown correctly for symlink'd kernels #640 - Changing a profile always adds an empty default interface. #661 ## [4.4.0rc3] 2022-12-23 @@ -57,7 +69,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The environment variable `WW_CONTAINER_SHELL` is defined in a `wwctl container shell` environment to indicate the container in use. #579 - Network interface configuration (`ifcfg`) files now include the - interface name and type. #574 + interface name and type. #457 ### Fixed diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..dfffc58e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,93 @@ +FROM opensuse/tumbleweed:latest as builder + +RUN zypper -n install --no-recommends git go1.18 libgpgme-devel &&\ + zypper -n install -t pattern devel_basis + +# now build the warewulf +COPY . /warewulf-src + +RUN cd /warewulf-src &&\ + make contclean &&\ + make genconfig \ + PREFIX=/usr \ + BINDIRa=/usr/bin \ + SYSCONFDIR=/etc \ + DATADIR=/usr/share \ + LOCALSTATEDIR=/var/lib \ + SHAREDSTATEDIR=/var/lib \ + MANDIR=/usr/share/man \ + INFODIR=/usr/share/info \ + DOCDIR=/usr/share/doc \ + SRVDIR=/var/lib \ + TFTPDIR=/srv/tftpboot \ + SYSTEMDDIR=/usr/lib/systemd/system \ + BASHCOMPDIR=/etc/warewulf/bash_completion.d/ \ + FIREWALLDDIR=/usr/lib/firewalld/services \ + WWCLIENTDIR=/warewulf &&\ + make lint &&\ + make &&\ + make install + +# now all again but just the bare install for running the stuff +FROM opensuse/tumbleweed:latest + +LABEL Description="Warewulf Base Container" +LABEL maintainer="Christian Goll " + +COPY --from=builder /usr/bin/wwctl /usr/bin/wwctl +COPY --from=builder /var/lib/warewulf /var/lib/warewulf +COPY --from=builder /usr/share/warewulf /usr/share/warewulf +COPY --from=builder /usr/lib/systemd/system/warewulfd.service /container-scripts/warewulfd.service +COPY --from=builder /etc/warewulf /etc/warewulf +COPY --from=builder /warewulf-src/container-scripts /container-scripts + +RUN zypper -n install \ + cpio \ + gzip \ + pigz \ + rsync \ + openssh-clients \ + less \ + dhcp-server \ + iproute2 \ + vim \ + yq \ + tftp \ + systemd \ + && \ + zypper clean -a && \ + systemctl enable dhcpd && \ + systemctl enable tftp.socket &&\ + export DHCPDCONF=/etc/sysconfig/dhcpd; test -e $DHCPDCONF && \ + sed -i 's/^DHCPD_INTERFACE=""/DHCPD_INTERFACE="ANY"/' $DHCPDCONF && \ + sed -i 's/^DHCPD_RUN_CHROOTED="yes"/DHCPD_RUN_CHROOTED="no"/' $DHCPDCONF && \ + WW4CONF=/etc/warewulf/warewulf.conf; test -e $WW4CONF && \ + yq e '.ipaddr |= "EMPTY"' -i $WW4CONF && \ + yq e '.netmask |= "EMPTY"' -i $WW4CONF && \ + yq e '.network |= "EMPTY"' -i $WW4CONF && \ + yq e '.dhcp.["range start"] |= "EMPTY"' -i $WW4CONF && \ + yq e '.dhcp.["range end"] |= "EMPTY"' -i $WW4CONF && \ + yq e '.nfs.enabled |= false' -i $WW4CONF && \ + mkdir -p /container && \ + cp -vr container-scripts/label-* \ + container-scripts/wwctl \ + container-scripts/warewulf.service \ + container-scripts/warewulf-container-manage.sh \ + container-scripts/config-warewulf \ + /container &&\ + mkdir -p /usr/share/bash_completion/completions/ &&\ + cp /etc/warewulf/bash_completion.d/warewulf /usr/share/bash_completion/completions/warewulf &&\ + mv -v container-scripts/ww4-config.service /etc/systemd/system/ &&\ + mv -v container-scripts/warewulfd.service /etc/systemd/system/ &&\ + systemctl enable ww4-config &&\ + systemctl enable warewulfd + +CMD [ "/usr/sbin/init" ] + +EXPOSE 67/udp 68/udp 69/udp 80 9873 + +LABEL INSTALL="/usr/bin/docker run --env IMAGE=IMAGE --rm --privileged -v /:/host IMAGE /bin/bash /container/label-install" +LABEL UNINSTALL="/usr/bin/docker run --rm --privileged -v /:/host IMAGE /bin/bash /container/label-uninstall" +LABEL PURGE="/usr/bin/docker run -ti --rm --privileged -v /:/host IMAGE /bin/bash /container/label-purge" +LABEL RUN="/usr/bin/docker run -d --replace --name \${NAME} --privileged --net=host -v /:/host -v /etc/warewulf:/etc/warewulf -v /var/lib/warewulf/:/var/lib/warewulf/ -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE}" + diff --git a/Makefile b/Makefile index b494dd69..2772c723 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all +.PHONY: all clean contclean -include Defaults.mk @@ -53,7 +53,7 @@ else endif # OS-Specific Service Locations -VARLIST += TFTPDIR FIREWALLDDIR SYSTEMDDIR +VARLIST += TFTPDIR FIREWALLDDIR SYSTEMDDIR BASHCOMPDIR SYSTEMDDIR ?= /usr/lib/systemd/system BASHCOMPDIR ?= /etc/bash_completion.d FIREWALLDDIR ?= /usr/lib/firewalld/services @@ -86,7 +86,7 @@ CONFIG := $(shell pwd) GO_TOOLS_BIN := $(addprefix $(TOOLS_BIN)/, $(notdir $(GO_TOOLS))) GO_TOOLS_VENDOR := $(addprefix vendor/, $(GO_TOOLS)) GOLANGCI_LINT := $(TOOLS_BIN)/golangci-lint -GOLANGCI_LINT_VERSION := v1.45.2 +GOLANGCI_LINT_VERSION := v1.50.0 # use GOPROXY for older git clients and speed up downloads GOPROXY ?= https://proxy.golang.org @@ -96,7 +96,7 @@ export GOPROXY WW_GO_BUILD_TAGS := containers_image_openpgp containers_image_ostree # Default target -all: config vendor wwctl wwclient bash_completion.d man_pages config_defaults print_defaults wwapid wwapic wwapird +all: config vendor wwctl wwclient bash_completion.d man_pages config_defaults print_defaults wwapid wwapic wwapird print_mnts # Validate source and build all packages build: lint test-it vet all @@ -188,6 +188,7 @@ files: all chmod 600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/etc/ssh/ssh* chmod 600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/etc/NetworkManager/system-connections/ww4-managed.ww chmod 644 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/etc/ssh/ssh*.pub.ww + chmod 644 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/warewulf/config.ww chmod 750 $(DESTDIR)$(WWOVERLAYDIR)/host install -m 0755 wwctl $(DESTDIR)$(BINDIR) install -m 0755 wwapic $(DESTDIR)$(BINDIR) @@ -247,6 +248,7 @@ config_defaults: vendor cmd/config_defaults/config_defaults.go print_defaults: vendor cmd/print_defaults/print_defaults.go cd cmd/print_defaults && go build -ldflags="-X 'github.com/hpcng/warewulf/internal/pkg/warewulfconf.ConfigFile=./etc/warewulf.conf'" -o ../../print_defaults + update_configuration: vendor cmd/update_configuration/update_configuration.go cd cmd/update_configuration && go build -ldflags="-X 'github.com/hpcng/warewulf/internal/pkg/warewulfconf.ConfigFile=./etc/warewulf.conf'\ -X 'github.com/hpcng/warewulf/internal/pkg/node.ConfigFile=./etc/nodes.conf'"\ @@ -256,10 +258,9 @@ warewulfconf: config_defaults ./config_defaults dist: vendor config - rm -rf .dist/$(WAREWULF)-$(VERSION) + rm -rf .dist/$(WAREWULF)-$(VERSION) $(WAREWULF)-$(VERSION).tar.gz mkdir -p .dist/$(WAREWULF)-$(VERSION) - cp -rap * .dist/$(WAREWULF)-$(VERSION)/ - find .dist/$(WAREWULF)-$(VERSION)/ -type f -name '*~' -delete + rsync -a --exclude=".*" --exclude "*~" * .dist/$(WAREWULF)-$(VERSION)/ cd .dist; tar -czf ../$(WAREWULF)-$(VERSION).tar.gz $(WAREWULF)-$(VERSION) rm -rf .dist @@ -291,7 +292,7 @@ wwapic: ## Build the sample wwapi client. wwapird: ## Build the rest api server (revese proxy to the grpc api server). go build -o ./wwapird internal/app/api/wwapird/wwapird.go -clean: +contclean: rm -f wwclient rm -f wwctl rm -rf .dist @@ -300,7 +301,6 @@ clean: rm -rf bash_completion.d rm -f man_page rm -rf man_pages - rm -rf vendor rm -f warewulf.spec rm -f config rm -f Defaults.mk @@ -310,6 +310,9 @@ clean: rm -f print_defaults rm -f etc/wwapi{c,d,rd}.conf +clean: contclean + rm -rf vendor + install: files install_wwclient debinstall: files debfiles diff --git a/cmd/update_configuration/update_configuration.go b/cmd/update_configuration/update_configuration.go index 2ddd79ae..c64de182 100644 --- a/cmd/update_configuration/update_configuration.go +++ b/cmd/update_configuration/update_configuration.go @@ -3,7 +3,6 @@ package main import ( "flag" "fmt" - "io/ioutil" "os" "reflect" @@ -44,7 +43,7 @@ func saveConf(conf interface{}) { os.Exit(1) } myprintf("writing configuration file %s as type %s\n", confFile, reflect.TypeOf(conf)) - err = ioutil.WriteFile(confFile, out, info.Mode()) + err = os.WriteFile(confFile, out, info.Mode()) if err != nil { myprintf("Could not write file: %s\n", err) os.Exit(1) @@ -136,7 +135,7 @@ func main() { os.Exit(1) } myprintf("Opening node configuration file: %s\n", confFile) - data, err := ioutil.ReadFile(confFile) + data, err := os.ReadFile(confFile) if err != nil { myprintf("Could open file %v\n", err) os.Exit(1) diff --git a/container-scripts/README.md b/container-scripts/README.md new file mode 100644 index 00000000..4d020015 --- /dev/null +++ b/container-scripts/README.md @@ -0,0 +1,61 @@ +# Warewulf on SUSE/openSUSE/ALP +This is the warewulf container including `tftp` and `dhcpd` services. It also shares the install run labels with the upstream container, but this container uses the rpm package of warewulf and not the upstream source. + +The containers used for deployment, the overlays and compressed images are stored not in the warewulf container but under `var/lib/warewulf` on the host. Also the configuration directory `/etc/warewulf`is created on the host. + +`dhpd` and `tftp` require systemd running also in the container. To provide these services the container must also run in the same network as the host and the container must also run in privileged mode. + +Additionally the `warewulf-container-manage.sh` script is included to manage the container via podman and the `wwctl` command to manage the warewulf itself. + +# Install the warewulf container + +## Prepare the host system + +It is heavlily advised that the host has a static ipv4 address. To configure this on ALP +you can use `nmcli` with the following command: +``` +nmcli connection modify "$(nmcli -t device | awk -F: '/ethernet/{print$4;exit}')" \ + ipv4.method manual \ + ipv4.addresses 192.168.1.250/24 \ + ipv4.gateway 192.168.1.1\ + ipv4.dns 182.168.1.1 +``` + +In order to run the warewulf container, you can just use the the '''runlable install''' on the container available in the registry. +``` +# podman container runlabel install registry.opensuse.org/suse/alp/workloads/tumbleweed_containerfiles/suse/alp/workloads/warewlf:latest +``` +This will create the directories `/var/lib/warewulf` and `/etc/warewulf` on the host system and populate it with necesarry configuraiton files, if this files are not present on the host. +On the first install the actual ip network settings are used as base values for the dynamic +network configuration in `warewulf.conf`. + +## Create the container + +The warewulf service is started with the command +``` +# podman container runlabel run registry.opensuse.org/suse/alp/workloads/tumbleweed_containerfiles/suse/alp/workloads/warewlf:latest +``` +Now the cluster can be managed with the `wwctl` command. + +## Remove the Container + +The container itself can be remove with the '''label uninstall''' which will remove the container and its scripts from the host. +``` +# podman container runlabel uninstall registry.opensuse.org/suse/alp/workloads/tumbleweed_containerfiles/suse/alp/workloads/warewlf:latest +``` + +This step *doesn't* remove the configuration of warewulf under `/etc/warewulf` and the containers with the overlays under `/var/lib/warewulf`. You can use the purge label to remove these directories. + +## Purge warewulf + +All components of warewulf including `/etc/warewulf` and `/var/lib/warewulf` can be removed the the '''label purge''' which can be called with +``` +# podman container runlabel purge registry.opensuse.org/suse/alp/workloads/tumbleweed_containerfiles/suse/alp/workloads/warewlf:latest +``` +Please note that the '''label purge''' inherits the label '''label uninstall'''. + + + +## More Info + +* [Warewulf](https://github.com/hpcng/warewulf) diff --git a/container-scripts/config-warewulf b/container-scripts/config-warewulf new file mode 100755 index 00000000..fd6254b2 --- /dev/null +++ b/container-scripts/config-warewulf @@ -0,0 +1,75 @@ +#!/bin/sh +# Helper functions +# Get the mask form prefix +cdr2mask() +{ + # Number of args to shift, 255..255, first non-255 byte, zeroes + set -- $(( 5 - ($1 / 8) )) 255 255 255 255 $(( (255 << (8 - ($1 % 8))) & 255 )) 0 0 0 + [ $1 -gt 1 ] && shift $1 || shift + echo ${1-0}.${2-0}.${3-0}.${4-0} +} +# Get the ip4 address of the netork +network_address() { + declare address prefix_length + IFS=/ read address prefix_length <<< "$1" + + declare -a octets + IFS=. read -a octets <<< "$address" + + declare mask + mask=$( printf "%08x" $(( (1 << 32) - (1 << (32 - prefix_length)) )) ) + + declare -i i + for i in {0..3}; do octets[$i]=$(( octets[i] & 16#${mask:2*i:2} )); done + + echo $( IFS=.; echo "${octets[*]}" ) +} +echo "-- WW4 CONFIGURAION $* --" + +# Make sure that a ip address was defined for out network so that +# we can configure dhcpd correctly +IP4CIDR=`ip addr | awk '/scope global/ {print $2;exit}'` +IP4=${IP4CIDR%/*} +IP4PREFIX=${IP4CIDR#*/} +IP4MASK=$(cdr2mask $IP4PREFIX) +IP4NET=$(network_address "$IP4/$IP4PREFIX") + +if [ "$IP4PREFIX" -gt 25 ] ; then + echo "ERROR: warewulf does at least a /25 network for dynamic addresses" + exit 1 +fi + +DYNSIZE=100 +DYNSTART=${IP4#*.*.*.} +DYNPRE=${IP4%.*} +DYNEND=$(( $DYNSTART + $DYNSIZE )) +if [ $DYNEND -gt 254 ] ; then + DYNEND=$(( $IPNET + 2 + $DYNSIZE )) + DYNSTART=$(( $IPNET + 2 )) +fi +DYNSTART="${DYNPRE}.${DYNSTART}" +DYNEND="${DYNPRE}.${DYNEND}" + + +WW4CONF=/etc/warewulf/warewulf.conf +if [ -e $WW4CONF ] ; then + test -n $IP4 && sed -i 's/^ipaddr: EMPTY/ipaddr: '$IP4'/' $WW4CONF + test -n $IP4MASK && sed -i 's/^netmask: EMPTY/netmask: '$IP4MASK'/' $WW4CONF + test -n $IP4NET && sed -i 's/^network: EMPTY/network: '$IP4NET'/' $WW4CONF + test -n $DYNSTART && sed -i 's/^ range start: EMPTY/ range start: '$DYNSTART'/' $WW4CONF + test -n $DYNEND && sed -i 's/^ range end: EMPTY/ range end: '$DYNEND'/' $WW4CONF + cat << EOF +ipaddr: $IP4 +netmask: $IP4MASK +network: $IP4NET + range start: $DYNSTART + range end: $DYNEND +EOF +fi + +# configure the services of the host by building the host overlay +echo "-- Running wwctl --" +# enable tftp if systemd is running +test -e /run/systemd/system && wwctl configure tftp +test -e /run/systemd/system && wwctl configure ssh +wwctl overlay build diff --git a/container-scripts/label-install b/container-scripts/label-install new file mode 100755 index 00000000..470ff0e0 --- /dev/null +++ b/container-scripts/label-install @@ -0,0 +1,82 @@ +#!/bin/sh -eu + +# This is the install script for warewulf when run in a privileged +# container. + +cd / +PATH="/usr/bin:/usr/sbin" +CONTAINER=warewulf +BINSCRIPT=${CONTAINER}-container-manage.sh +WWCTL=wwctl +OVERLAYDIR=/var/lib/warewulf/overlays +CHROOTDIR=/var/lib/warewulf/chroots +CONTAINERDIR=/var/lib/warewulf/container +WAREWULFCONF=/etc/warewulf +BASHCOMPLETION=/usr/share/bash_completion/completions/w* +AUTHKEYDIR=/root/.ssh + +echo "LABEL INSTALL" +# ensure all scripts will be present on the host +copy_to_usr_local_bin() { +SCRIPT=$1 +BASEDIR=`dirname $SCRIPT` +mkdir -p $BASEDIR +if [ ! -e /host/usr/local/bin/${SCRIPT} ]; then + echo "copy /container/${SCRIPT} in /host/usr/local/bin/" + rsync -u /container/${SCRIPT} /host/usr/local/bin/${SCRIPT} + chmod 755 /host/usr/local/bin/${SCRIPT} +else + echo "/host/usr/local/bin/${SCRIPT} already exist, will not update it" +fi +} + +copy_to_etc() { +CONF=$1 +BASEDIR=`dirname $CONF` +mkdir -p $BASEDIR +if [ ! -e /host/etc/${CONF} ]; then + echo "copy /container/${CONF} in /host/etc/" + rsync -u /container/${CONF} /host/etc/${CONF} +else + echo "/host/etc/${CONF} already exist, will not update it" +fi +} +sync_dir() { + DIR=$1 + test -e /host/${DIR} || mkdir -pv /host/${DIR} + test -e /host/${DIR} && (rsync -au --ignore-existing ${DIR} `dirname /host/${DIR}`; echo "updating $DIR") +} + +# For podman, cp a systemd unit for starting on boot +if [ ! -e /host/etc/systemd/system/${CONTAINER}.service ]; then + mkdir -p /host/etc/systemd/system/ + rsync -u /container/${CONTAINER}.service /host/etc/systemd/system/${CONTAINER}.service +else + echo "/host/etc/systemd/system/${CONTAINER}.service already exist" +fi + +# create the dirs +mkdir -p /host/etc/warewulf +mkdir -p /host/var/lib/warewulf + +copy_to_usr_local_bin ${WWCTL} +copy_to_usr_local_bin ${BINSCRIPT} + +# now sync hosts and overlays +sync_dir $OVERLAYDIR +sync_dir $CHROOTDIR +sync_dir $WAREWULFCONF + +# bash completion +if [ ! -e /host/etc/bash_completion.d/warewulf ] ; then + mkdir -p /host/etc/bash_completion.d + cp $BASHCOMPLETION /host/etc/bash_completion.d/ +fi + +# containerdir +mkdir -p /host/$CONTAINERDIR + +# authorized keys +mkdir -p /root/.ssh/ +echo "Copy authorized keys to container" +test -e /host/${AUTHKEYDIR}/*pub && cp -v /host/${AUTHKEYDIR}/*pub ${AUTHKEYDIR} || echo "no public keys found" diff --git a/container-scripts/label-purge b/container-scripts/label-purge new file mode 100755 index 00000000..f4dc9118 --- /dev/null +++ b/container-scripts/label-purge @@ -0,0 +1,34 @@ +#!/bin/bash +OVERLAYDIR=/var/lib/warewulf/overlays +CHROOTDIR=/var/lib/warewulf/chroots +OCIDIR=/var/lib/warewulf/oci +CONTAINERDIR=/var/lib/warewulf/container +WAREWULFCONF=/etc/warewulf +BASEDIR=/var/lib/warewulf +cat >&2 << EOF +WARNING: +Purging all warewulf configurations, containers aka node images and overlays +Wating for 10s, press any key to abort +EOF + +count=0 +while true ; do + if read -t 0; then # Input ready + read -n 1 char + echo "Aborting purge" + exit 0 + else # No input + echo -n '.' + sleep 1 + count=$(( $count + 1)) + if [ $count -eq 10 ] ; then + echo + break + fi + fi +done +echo "PURGING" +/container/label-uninstall +rm -rv /host/$WAREWULFCONF /host/$OVERLAYDIR /host/$CHROOTDIR /host/$CONTAINERDIR /host/$OCIDIR +rmdir /host/$BASEDIR + diff --git a/container-scripts/label-uninstall b/container-scripts/label-uninstall new file mode 100755 index 00000000..915e65f1 --- /dev/null +++ b/container-scripts/label-uninstall @@ -0,0 +1,20 @@ +#!/bin/sh -eu + +# This is the uninstall script for grafana when run in a privileged +# container. + +CONTAINER=warewulf +cd / +PATH="/usr/bin:/usr/sbin" + +if [ ! -d /host/etc ] || [ ! -d /host/usr/local/bin ]; then + echo "${CONTAINER}-uninstall: host file system is not mounted at /host" + exit 1 +fi + +# removing installed files +echo "LABEL UNINSTALL: Removing all files" +rm -vf /host/usr/local/bin/wwctl +rm -vf /host/usr/local/bin/warewulf-container-manage.sh +rm -vf /host/etc/systemd/system/${CONTAINER}.service +rm -vf /host/usr/share/bash_completion/completions/wwctl diff --git a/container-scripts/warewulf-container-manage.sh b/container-scripts/warewulf-container-manage.sh new file mode 100755 index 00000000..abb94001 --- /dev/null +++ b/container-scripts/warewulf-container-manage.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# quick script to manage the container +CONTAINER_NAME=warewulf + +create_container() { +podman create \ + --name ${CONTAINER_NAME} \ + --tls-verify=false \ + --network host \ + ${IMAGE} +} + +run_container() { +podman run \ + --name ${CONTAINER_NAME} \ + --rm -ti \ + --network host \ + --entrypoint bash \ + ${IMAGE} +} + +if [ -z "$1" ]; then +echo " +First ARG is mandatory: +$0 [create|start|stop|rm|rmcache|run|bash|logs|install|uninstall] + +CONTAINER_NAME: '${CONTAINER_NAME}' + +DEPLOYMENT: +create + Pull the image and create the container automatically + +install + install needed files on the host to manage '${CONTAINER_NAME}' container + (in /usr/local/bin and /etc) + +start + Start the container '${CONTAINER_NAME}' + +REMOVAL: +uninstall + uninstall all needed files on the host to manage '${CONTAINER_NAME}' container + +stop + stop the container '${CONTAINER_NAME}' + +rm + delete the container '${CONTAINER_NAME}' + +rmcache + remove the container image in cache ${IMAGE} + +DEBUG: +run + podman run container '${CONTAINER_NAME}' + +bash + go with /bin/bash command inside '${CONTAINER_NAME}' + +logs + see log of container '${CONTAINER_NAME}' + + " + exit 1 +fi + +########### +# MAIN +########### +set -euxo pipefail + +case $1 in + start) + podman start ${CONTAINER_NAME} + podman ps | grep ${CONTAINER_NAME} + ;; + stop) + podman stop ${CONTAINER_NAME} + podman ps | grep ${CONTAINER_NAME} + ;; + rm) + set +e + podman stop ${CONTAINER_NAME} + podman rm ${CONTAINER_NAME} + ;; + create) + create_container + ;; + run) + run_container + ;; + rmcache) + podman rmi ${IMAGE} + ;; + logs) + podman logs ${CONTAINER_NAME} + ;; + bash) + set +e + podman exec -ti ${CONTAINER_NAME} $@ + ;; + install) + podman run --env IMAGE=${IMAGE} --rm --privileged -v /:/host ${IMAGE} /bin/bash /container/label-install + ;; + uninstall) + podman run --env IMAGE=${IMAGE} --rm --privileged -v /:/host ${IMAGE} /bin/bash /container/label-uninstall + ;; +esac diff --git a/container-scripts/warewulf.service b/container-scripts/warewulf.service new file mode 100644 index 00000000..41737835 --- /dev/null +++ b/container-scripts/warewulf.service @@ -0,0 +1,25 @@ +[Unit] +Description=warewulf daemon container +Documentation=https://build.opensuse.org/package/show/SUSE:ALP:Workloads/warewulf-container +After=network-online.target +After=local-fs.target +Wants=network-online.target +StartLimitIntervalSec=40 +StartLimitBurst=5 + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=always +RestartSec=1s +TimeoutStopSec=70 +#Environment=WAREWULF_IMAGE_PATH=registry.opensuse.org/suse/alp/workloads/tumbleweed_containerfiles/suse/alp/workloads/warewulf:latest +Environment=WAREWULF_IMAGE_PATH=warewulf:latest +ExecStartPre=-/usr/bin/podman container runlabel --name warewulf install ${WAREWULF_IMAGE_PATH} +ExecStart=/usr/bin/podman container runlabel --name warewulf run ${WAREWULF_IMAGE_PATH} +ExecStop=/usr/bin/podman container stop warewulf +ExecStopPost=/usr/bin/podman container rm warewulf +Type=notify +NotifyAccess=all + +[Install] +WantedBy=multi-user.target diff --git a/container-scripts/ww4-config.service b/container-scripts/ww4-config.service new file mode 100644 index 00000000..3d21a547 --- /dev/null +++ b/container-scripts/ww4-config.service @@ -0,0 +1,12 @@ +[Unit] +Description=Warewulf container configuration +After=local-fs.target +Before=warewulfd.service +Before=dhcpd + +[Service] +Type=oneshot +ExecStart=/container/config-warewulf + +[Install] +WantedBy=multi-user.target diff --git a/container-scripts/wwctl b/container-scripts/wwctl new file mode 100755 index 00000000..1eab34b7 --- /dev/null +++ b/container-scripts/wwctl @@ -0,0 +1,6 @@ +#!/bin/bash + +set -euxo pipefail + +# Run the domain +podman exec -ti ${CONTAINER_NAME} wwctl $@ diff --git a/etc/examples/static-dhcpd.conf.ww b/etc/examples/static-dhcpd.conf.ww deleted file mode 100644 index e3c2a63d..00000000 --- a/etc/examples/static-dhcpd.conf.ww +++ /dev/null @@ -1,43 +0,0 @@ -{{- if .Dhcp.Enabled }} -# This file is autogenerated by warewulf -# Host: {{.BuildHost}} -# Time: {{.BuildTime}} -# Source: {{.BuildSource}} -allow booting; -allow bootp; -ddns-update-style interim; -authoritative; - -option space ipxe; - -# Tell iPXE to not wait for ProxyDHCP requests to speed up boot. -option ipxe.no-pxedhcp code 176 = unsigned integer 8; -option ipxe.no-pxedhcp 1; - -option architecture-type code 93 = unsigned integer 16; - -if exists user-class and option user-class = "iPXE" { - filename "http://{{$.Ipaddr}}:{{.Warewulf.Port}}/ipxe/${mac:hexhyp}"; -} else { - if option architecture-type = 00:0B { - filename "/warewulf/arm64.efi"; - } elsif option architecture-type = 00:09 { - filename "/warewulf/x86_64.efi"; - } elsif option architecture-type = 00:00 { - filename "/warewulf/x86_64.kpxe"; - } -} - - -subnet {{$.Network}} netmask {{$.Netmask}} { - next-server {{$.Ipaddr}}; -} - -{{range $nodes := .Nodes}} -host {{$nodes.Id.Get}} { - hardware ethernet {{$nodes.NetDevs.eth0.Hwaddr.Get}}; - fixed-address {{$nodes.NetDevs.eth0.Ipaddr.Get}}; -} -{{end}} - -{{- end }} diff --git a/etc/warewulf.conf b/etc/warewulf.conf index d0a0c4db..a5b9a346 100644 --- a/etc/warewulf.conf +++ b/etc/warewulf.conf @@ -29,3 +29,7 @@ nfs: mount options: defaults mount: false systemd name: nfs-server +container mounts: + - source: /etc/resolv.conf + dest: /etc/resolv.conf + readonly: true diff --git a/include/systemd/warewulfd.service.in b/include/systemd/warewulfd.service.in index 27b121a4..85632559 100644 --- a/include/systemd/warewulfd.service.in +++ b/include/systemd/warewulfd.service.in @@ -10,8 +10,8 @@ User=root Group=root ExecStart=@BINDIR@/wwctl server start -ExecReload=/usr/bin/wwctl server reload -ExecStop=/usr/bin/wwctl server stop +ExecReload=@BINDIR@/wwctl server reload +ExecStop=@BINDIR@/wwctl server stop PIDFile=/var/run/warewulfd.pid Restart=always diff --git a/internal/app/api/wwapic/wwapic.go b/internal/app/api/wwapic/wwapic.go index f4c09a63..65cd85b5 100644 --- a/internal/app/api/wwapic/wwapic.go +++ b/internal/app/api/wwapic/wwapic.go @@ -5,8 +5,8 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "io/ioutil" "log" + "os" "path" "time" @@ -42,7 +42,7 @@ func main() { // Load the CA cert. var cacert []byte - cacert, err = ioutil.ReadFile(config.TlsConfig.CaCert) + cacert, err = os.ReadFile(config.TlsConfig.CaCert) if err != nil { log.Fatalf("Failed to load cacert. err: %s\n", err) } @@ -86,4 +86,4 @@ func main() { } log.Printf("Version Response: %v\n", response) -} \ No newline at end of file +} diff --git a/internal/app/api/wwapid/wwapid.go b/internal/app/api/wwapid/wwapid.go index 7d6a1de4..1cea854f 100644 --- a/internal/app/api/wwapid/wwapid.go +++ b/internal/app/api/wwapid/wwapid.go @@ -6,7 +6,6 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "io/ioutil" "log" "net" "os" @@ -60,7 +59,7 @@ func main() { // Load the CA cert. var cacert []byte - cacert, err = ioutil.ReadFile(config.TlsConfig.CaCert) + cacert, err = os.ReadFile(config.TlsConfig.CaCert) if err != nil { log.Fatalf("Failed to load cacert. err: %s\n", err) } diff --git a/internal/app/api/wwapird/wwapird.go b/internal/app/api/wwapird/wwapird.go index 4af35e8b..dcbaecf1 100644 --- a/internal/app/api/wwapird/wwapird.go +++ b/internal/app/api/wwapird/wwapird.go @@ -4,11 +4,11 @@ import ( "context" "crypto/tls" "crypto/x509" + "os" "flag" "fmt" "log" - "io/ioutil" "net/http" "github.com/golang/glog" @@ -21,8 +21,9 @@ import ( gw "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" - "github.com/hpcng/warewulf/internal/pkg/buildconfig" "path" + + "github.com/hpcng/warewulf/internal/pkg/buildconfig" ) func run() error { @@ -56,7 +57,7 @@ func run() error { // Load the CA cert. var cacert []byte - cacert, err = ioutil.ReadFile(config.ClientTlsConfig.CaCert) + cacert, err = os.ReadFile(config.ClientTlsConfig.CaCert) if err != nil { log.Fatalf("Failed to load cacert. err: %s\n", err) } @@ -109,4 +110,4 @@ func main() { if err := run(); err != nil { glog.Fatal(err) } -} \ No newline at end of file +} diff --git a/internal/app/wwclient/root.go b/internal/app/wwclient/root.go index 01807a28..dc06e684 100644 --- a/internal/app/wwclient/root.go +++ b/internal/app/wwclient/root.go @@ -3,7 +3,6 @@ package wwclient import ( "errors" "fmt" - "io/ioutil" "log" "net" "net/http" @@ -122,7 +121,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { x := smbiosDump.SystemEnclosure() tag := strings.ReplaceAll(x.AssetTagNumber(), " ", "_") - cmdline, err := ioutil.ReadFile("/proc/cmdline") + cmdline, err := os.ReadFile("/proc/cmdline") if err != nil { wwlog.Error("Could not read from /proc/cmdline: %s", err) os.Exit(1) diff --git a/internal/app/wwctl/container/exec/child/main.go b/internal/app/wwctl/container/exec/child/main.go index dda8b757..bf32ad17 100644 --- a/internal/app/wwctl/container/exec/child/main.go +++ b/internal/app/wwctl/container/exec/child/main.go @@ -7,11 +7,15 @@ import ( "fmt" "os" "path" + "path/filepath" + "strings" "syscall" - "time" "github.com/hpcng/warewulf/internal/pkg/container" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/overlay" "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/warewulfconf" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -29,43 +33,130 @@ func CobraRunE(cmd *cobra.Command, args []string) error { wwlog.Error("Unknown Warewulf container: %s", containerName) os.Exit(1) } + conf, err := warewulfconf.New() + if err != nil { + wwlog.Verbose("Couldn't get warewulf ocnfiguration: %s", err) + } + mountPts := conf.MountsContainer + mountPts = append(container.InitMountPnts(binds), mountPts...) + // check for valid mount points + lowerObjects := checkMountPoints(containerName, mountPts) + if len(lowerObjects) != 0 { + if tempDir == "" { + tempDir, err = os.MkdirTemp(os.TempDir(), "overlay") + if err != nil { + wwlog.Warn("couldn't create temp dir for overlay", err) + lowerObjects = []string{} + tempDir = "" + } + } + // need to create a overlay, where the lower layer contains + // the missing mount points + if tempDir != "" { + wwlog.Verbose("for ephermal mount use tempdir %s", tempDir) + // ignore errors as we are doomed if a tmp dir couldn't be written + _ = os.Mkdir(path.Join(tempDir, "work"), os.ModePerm) + _ = os.Mkdir(path.Join(tempDir, "lower"), os.ModePerm) + _ = os.Mkdir(path.Join(tempDir, "nodeoverlay"), os.ModePerm) + for _, obj := range lowerObjects { + newFile := "" + if !strings.HasSuffix(obj, "/") { + newFile = filepath.Base(obj) + obj = filepath.Dir(obj) + } + err = os.MkdirAll(filepath.Join(tempDir, "lower", obj), os.ModePerm) + if err != nil { + wwlog.Warn("couldn't create directory for mounts: %s", err) + } + if newFile != "" { + desc, err := os.Create(filepath.Join(tempDir, "lower", obj, newFile)) + if err != nil { + wwlog.Warn("couldn't create directory for mounts: %s", err) + } + defer desc.Close() + } + } + } + } containerPath := container.RootFsDir(containerName) - fileStat, _ := os.Stat(path.Join(containerPath, "/etc/passwd")) - unixStat := fileStat.Sys().(*syscall.Stat_t) - passwdTime := time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec)) - fileStat, _ = os.Stat(path.Join(containerPath, "/etc/group")) - unixStat = fileStat.Sys().(*syscall.Stat_t) - groupTime := time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec)) - wwlog.Debug("passwd: %v", passwdTime) - wwlog.Debug("group: %v", groupTime) - - err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, "") + err = syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, "") if err != nil { return errors.Wrap(err, "failed to mount") } + ps1Str := fmt.Sprintf("[%s] Warewulf> ", containerName) + if len(lowerObjects) != 0 && nodename == "" { + options := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", + path.Join(tempDir, "lower"), containerPath, path.Join(tempDir, "work")) + wwlog.Debug("overlay options: %s", options) + err = syscall.Mount("overlay", containerPath, "overlay", 0, options) + if err != nil { + wwlog.Warn(fmt.Sprintf("Couldn't create overlay for ephermal mount points: %s", err)) + } + } else if nodename != "" { + nodeDB, err := node.New() + if err != nil { + wwlog.Error("Could not open node configuration: %s", err) + os.Exit(1) + } + + nodes, err := nodeDB.FindAllNodes() + if err != nil { + wwlog.Error("Could not get node list: %s", err) + os.Exit(1) + } + nodes = node.FilterByName(nodes, []string{nodename}) + if len(nodes) != 1 { + wwlog.Error("No single node idendified with %s", nodename) + os.Exit(1) + } + overlays := nodes[0].SystemOverlay.GetSlice() + overlays = append(overlays, nodes[0].RuntimeOverlay.GetSlice()...) + err = overlay.BuildOverlayIndir(nodes[0], overlays, path.Join(tempDir, "nodeoverlay")) + if err != nil { + wwlog.Error("Could not build overlay: %s", err) + os.Exit(1) + } + options := fmt.Sprintf("lowerdir=%s:%s:%s", + path.Join(tempDir, "lower"), containerPath, path.Join(tempDir, "nodeoverlay")) + wwlog.Debug("overlay options: %s", options) + err = syscall.Mount("overlay", containerPath, "overlay", 0, options) + if err != nil { + wwlog.Warn(fmt.Sprintf("Couldn't create overlay for node render overlay: %s", err)) + os.Exit(1) + } + ps1Str = fmt.Sprintf("[%s|ro|%s] Warewulf> ", containerName, nodename) + } + if !util.IsWriteAble(containerPath) && nodename == "" { + wwlog.Verbose("mounting %s ro", containerPath) + ps1Str = fmt.Sprintf("[%s|ro] Warewulf> ", containerName) + err = syscall.Mount(containerPath, containerPath, "", syscall.MS_BIND, "") + if err != nil { + return errors.Wrap(err, "failed to prepare bind mount") + } + err = syscall.Mount(containerPath, containerPath, "", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, "") + if err != nil { + return errors.Wrap(err, "failed to remount ro") + } + } err = syscall.Mount("/dev", path.Join(containerPath, "/dev"), "", syscall.MS_BIND, "") if err != nil { return errors.Wrap(err, "failed to mount /dev") } - for _, b := range binds { - var source string - var dest string - - bind := util.SplitValidPaths(b, ":") - source = bind[0] - - if len(bind) == 1 { - dest = source - } else { - dest = bind[1] - } - - err := syscall.Mount(source, path.Join(containerPath, dest), "", syscall.MS_BIND, "") + for _, mntPnt := range mountPts { + err = syscall.Mount(mntPnt.Source, path.Join(containerPath, mntPnt.Dest), "", syscall.MS_BIND, "") if err != nil { - fmt.Printf("BIND ERROR: %s\n", err) - os.Exit(1) + wwlog.Warn("Couldn't mount %s to %s: %s", mntPnt.Source, mntPnt.Dest, err) + } else if mntPnt.ReadOnly { + err = syscall.Mount(mntPnt.Source, path.Join(containerPath, mntPnt.Dest), "", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, "") + if err != nil { + wwlog.Warn("failed to following mount readonly: %s", mntPnt.Source) + } else { + wwlog.Verbose("mounted readonly from host to container: %s:%s", mntPnt.Source, mntPnt.Dest) + } + } else { + wwlog.Verbose("mounted from host to container: %s:%s", mntPnt.Source, mntPnt.Dest) } } @@ -84,27 +175,39 @@ func CobraRunE(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "failed to mount proc") } - os.Setenv("PS1", fmt.Sprintf("[%s] Warewulf> ", containerName)) + os.Setenv("PS1", ps1Str) os.Setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin") os.Setenv("HISTFILE", "/dev/null") - err = syscall.Exec(args[1], args[1:], os.Environ()) - if err != nil { - wwlog.Error("%s", err) - os.Exit(1) - } - fileStat, _ = os.Stat(path.Join(containerPath, "/etc/passwd")) - unixStat = fileStat.Sys().(*syscall.Stat_t) - if passwdTime.Before(time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec))) { - wwlog.Warn("/etc/passwd has been modified, maybe you want to run syncuser") - } - wwlog.Debug("passwd: %v", time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec))) - fileStat, _ = os.Stat(path.Join(containerPath, "/etc/group")) - unixStat = fileStat.Sys().(*syscall.Stat_t) - if groupTime.Before(time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec))) { - wwlog.Warn("/etc/group has been modified, maybe you want to run syncuser") - } - wwlog.Debug("group: %v", time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec))) - + _ = syscall.Exec(args[1], args[1:], os.Environ()) + /* + Exec replaces the actual program, so nothing to do here afterwards + */ return nil } + +/* +Check if the bind mount points exists in the given container. Returns +the invalid mount points. Directories always have '/' as suffix +*/ +func checkMountPoints(containerName string, binds []*warewulfconf.MountEntry) (overlayObjects []string) { + overlayObjects = []string{} + for _, b := range binds { + _, err := os.Stat(b.Source) + if err != nil { + wwlog.Debug("Couldn't stat %s create no mount point in container", b.Source) + continue + } + wwlog.Debug("Checking in container for %s", path.Join(container.RootFsDir(containerName), b.Dest)) + if _, err = os.Stat(path.Join(container.RootFsDir(containerName), b.Dest)); err != nil { + if os.IsNotExist(err) { + if util.IsDir(b.Dest) && !strings.HasSuffix(b.Dest, "/") { + b.Dest += "/" + } + overlayObjects = append(overlayObjects, b.Dest) + wwlog.Debug("Container %s, needs following path: %s", containerName, b.Dest) + } + } + } + return overlayObjects +} diff --git a/internal/app/wwctl/container/exec/child/root.go b/internal/app/wwctl/container/exec/child/root.go index 0b7dde7c..d7060995 100644 --- a/internal/app/wwctl/container/exec/child/root.go +++ b/internal/app/wwctl/container/exec/child/root.go @@ -5,17 +5,21 @@ import "github.com/spf13/cobra" var ( baseCmd = &cobra.Command{ DisableFlagsInUseLine: true, - Use: "__child", - Hidden: true, - RunE: CobraRunE, - Args: cobra.MinimumNArgs(1), - FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, + Use: "__child", + Hidden: true, + RunE: CobraRunE, + Args: cobra.MinimumNArgs(1), + FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, } - binds []string + binds []string + tempDir string + nodename string ) func init() { - baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, "bind points") + baseCmd.Flags().StringVarP(&nodename, "node", "n", "", "create ro overlay for given node") + baseCmd.Flags().StringArrayVarP(&binds, "bind", "b", []string{}, "bind points") + baseCmd.Flags().StringVar(&tempDir, "tempdir", "", "tempdir") } // GetRootCommand returns the root cobra.Command for the application. diff --git a/internal/app/wwctl/container/exec/main.go b/internal/app/wwctl/container/exec/main.go index 722ba479..bc622de2 100644 --- a/internal/app/wwctl/container/exec/main.go +++ b/internal/app/wwctl/container/exec/main.go @@ -17,9 +17,26 @@ import ( "github.com/spf13/cobra" ) +/* +fork off a process with a new PID space +*/ func runContainedCmd(args []string) error { + var err error + if tempDir == "" { + tempDir, err = os.MkdirTemp(os.TempDir(), "overlay") + if err != nil { + wwlog.Warn("couldn't create temp dir for overlay", err) + } + defer func() { + err = os.RemoveAll(tempDir) + if err != nil { + wwlog.Warn("Couldn't remove temp dir for ephermal mounts:", err) + } + }() + } + logStr := fmt.Sprint(wwlog.GetLogLevel()) wwlog.Verbose("Running contained command: %s", args[1:]) - c := exec.Command("/proc/self/exe", append([]string{"container", "exec", "__child"}, args...)...) + c := exec.Command("/proc/self/exe", append([]string{"--loglevel", logStr, "--tempdir", tempDir, "container", "exec", "__child"}, args...)...) c.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS, @@ -30,6 +47,11 @@ func runContainedCmd(args []string) error { if err := c.Run(); err != nil { fmt.Printf("Command exited non-zero, not rebuilding/updating VNFS image\n") + // defer is not called before os.Exit(0) + err = os.RemoveAll(tempDir) + if err != nil { + wwlog.Warn("Couldn't remove temp dir for ephermal mounts:", err) + } os.Exit(0) } return nil @@ -38,6 +60,8 @@ func runContainedCmd(args []string) error { func CobraRunE(cmd *cobra.Command, args []string) error { containerName := args[0] + os.Setenv("WW_CONTAINER_SHELL", containerName) + var allargs []string if !container.ValidSource(containerName) { @@ -48,6 +72,9 @@ func CobraRunE(cmd *cobra.Command, args []string) error { for _, b := range binds { allargs = append(allargs, "--bind", b) } + if nodeName != "" { + allargs = append(allargs, "--node", nodeName) + } allargs = append(allargs, args...) containerPath := container.RootFsDir(containerName) @@ -109,3 +136,10 @@ func CobraRunE(cmd *cobra.Command, args []string) error { return nil } +func SetBinds(myBinds []string) { + binds = append(binds, myBinds...) +} + +func SetNode(myNode string) { + nodeName = myNode +} diff --git a/internal/app/wwctl/container/exec/root.go b/internal/app/wwctl/container/exec/root.go index db8bea41..e5fc577b 100644 --- a/internal/app/wwctl/container/exec/root.go +++ b/internal/app/wwctl/container/exec/root.go @@ -27,12 +27,16 @@ var ( } SyncUser bool binds []string + tempDir string + nodeName string ) func init() { baseCmd.AddCommand(child.GetCommand()) baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, "Bind a local path into the container (must exist)") baseCmd.PersistentFlags().BoolVar(&SyncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to container") + baseCmd.PersistentFlags().StringVar(&tempDir, "tempdir", "", "Use tempdir for constructing the overlay fs (only used if mount points don't exist in container)") + baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node") } // GetRootCommand returns the root cobra.Command for the application. diff --git a/internal/app/wwctl/container/shell/main.go b/internal/app/wwctl/container/shell/main.go index 04a095ec..b7c3fbde 100644 --- a/internal/app/wwctl/container/shell/main.go +++ b/internal/app/wwctl/container/shell/main.go @@ -4,11 +4,10 @@ package shell import ( - "fmt" "os" - "os/exec" - "syscall" + "path" + cntexec "github.com/hpcng/warewulf/internal/app/wwctl/container/exec" "github.com/hpcng/warewulf/internal/pkg/container" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" @@ -23,28 +22,32 @@ func CobraRunE(cmd *cobra.Command, args []string) error { wwlog.Error("Unknown Warewulf container: %s", containerName) os.Exit(1) } - - for _, b := range binds { - allargs = append(allargs, "--bind", b) - } - allargs = append(allargs, args...) - allargs = append(allargs, "/usr/bin/bash") - - c := exec.Command("/proc/self/exe", append([]string{"container", "exec"}, allargs...)...) - - //c := exec.Command("/bin/sh") - c.SysProcAttr = &syscall.SysProcAttr{ - Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS, - } - c.Stdin = os.Stdin - c.Stdout = os.Stdout - c.Stderr = os.Stderr - - os.Setenv("WW_CONTAINER_SHELL", containerName) - - if err := c.Run(); err != nil { - fmt.Println(err) + /* + for _, b := range binds { + allargs = append(allargs, "--bind", b) + } + */ + shellName := os.Getenv("SHELL") + if !container.ValidSource(containerName) { + wwlog.Error("Unknown Warewulf container: %s", containerName) os.Exit(1) } - return nil + var shells []string + if shellName == "" { + shells = append(shells, "/bin/bash") + } else { + shells = append(shells, shellName, "/bin/bash") + } + for _, s := range shells { + if _, err := os.Stat(path.Join(container.RootFsDir(containerName), s)); err == nil { + shellName = s + break + } + } + args = append(args, shellName) + allargs = append(allargs, args...) + wwlog.Debug("Calling exec with args: %s", allargs) + cntexec.SetBinds(binds) + cntexec.SetNode(nodeName) + return cntexec.CobraRunE(cmd, allargs) } diff --git a/internal/app/wwctl/container/shell/root.go b/internal/app/wwctl/container/shell/root.go index 59f6c56b..dcec3f06 100644 --- a/internal/app/wwctl/container/shell/root.go +++ b/internal/app/wwctl/container/shell/root.go @@ -22,11 +22,13 @@ var ( }, FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, } - binds []string + binds []string + nodeName string ) func init() { baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, "Bind a local path into the container (must exist)") + baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node") } // GetRootCommand returns the root cobra.Command for the application. diff --git a/internal/app/wwctl/node/edit/main.go b/internal/app/wwctl/node/edit/main.go index 9307edef..0683f931 100644 --- a/internal/app/wwctl/node/edit/main.go +++ b/internal/app/wwctl/node/edit/main.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "fmt" "io" - "io/ioutil" "os" "strings" @@ -39,7 +38,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { nodeMap := make(map[string]*node.NodeConf) // got proper yaml back _ = yaml.Unmarshal([]byte(nodeListMsg.NodeConfMapYaml), nodeMap) - file, err := ioutil.TempFile("/tmp", "ww4NodeEdit*.yaml") + file, err := os.CreateTemp(os.TempDir(), "ww4NodeEdit*.yaml") if err != nil { wwlog.Error("Could not create temp file:%s \n", err) } diff --git a/internal/app/wwctl/overlay/build/main.go b/internal/app/wwctl/overlay/build/main.go index 031a2375..30babd08 100644 --- a/internal/app/wwctl/overlay/build/main.go +++ b/internal/app/wwctl/overlay/build/main.go @@ -78,7 +78,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { } - if BuildHost || (!BuildHost && !BuildNodes && len(args) == 0 && controller.Warewulf.EnableHostOverlay) { + if BuildHost && controller.Warewulf.EnableHostOverlay { err := overlay.BuildHostOverlay() if err != nil { wwlog.Warn("host overlay could not be built: %s", err) diff --git a/internal/app/wwctl/overlay/edit/main.go b/internal/app/wwctl/overlay/edit/main.go index b093b461..4ef1c019 100644 --- a/internal/app/wwctl/overlay/edit/main.go +++ b/internal/app/wwctl/overlay/edit/main.go @@ -80,13 +80,13 @@ func CobraRunE(cmd *cobra.Command, args []string) error { } fileDesc1, err := os.OpenFile(overlayFile, os.O_RDWR, os.FileMode(PermMode)) if err != nil { - wwlog.Warn("Could not open file for editing: %s", err) + wwlog.Verbose("Files doesn't exist or can't be created: %s", err) } defer fileDesc1.Close() _, _ = fileDesc1.Seek(0, 0) hasher := sha256.New() if _, err := io.Copy(hasher, fileDesc1); err != nil { - wwlog.Error("Problems getting checksum of file %s\n", err) + wwlog.Verbose("Problems getting checksum of file %s\n", err) } sum1 := hex.EncodeToString(hasher.Sum(nil)) fileDesc1.Close() diff --git a/internal/app/wwctl/overlay/edit/root.go b/internal/app/wwctl/overlay/edit/root.go index 2e265a60..0fcc47a2 100644 --- a/internal/app/wwctl/overlay/edit/root.go +++ b/internal/app/wwctl/overlay/edit/root.go @@ -21,13 +21,11 @@ var ( return list, cobra.ShellCompDirectiveNoFileComp }, } - ListFiles bool CreateDirs bool PermMode int32 ) func init() { - baseCmd.PersistentFlags().BoolVarP(&ListFiles, "files", "f", false, "List files contained within a given overlay") baseCmd.PersistentFlags().BoolVarP(&CreateDirs, "parents", "p", false, "Create any necessary parent directories") baseCmd.PersistentFlags().Int32VarP(&PermMode, "mode", "m", 0755, "Permission mode for directory") } diff --git a/internal/app/wwctl/overlay/imprt/root.go b/internal/app/wwctl/overlay/imprt/root.go index 4dc89acb..fd9b957f 100644 --- a/internal/app/wwctl/overlay/imprt/root.go +++ b/internal/app/wwctl/overlay/imprt/root.go @@ -22,12 +22,10 @@ var ( return list, cobra.ShellCompDirectiveNoFileComp }, } - PermMode int32 NoOverlayUpdate bool ) func init() { - baseCmd.PersistentFlags().Int32VarP(&PermMode, "mode", "m", 0755, "Permission mode for directory") baseCmd.PersistentFlags().BoolVarP(&NoOverlayUpdate, "noupdate", "n", false, "Don't update overlays") } diff --git a/internal/app/wwctl/overlay/list/main.go b/internal/app/wwctl/overlay/list/main.go index 19ac1818..ac02a619 100644 --- a/internal/app/wwctl/overlay/list/main.go +++ b/internal/app/wwctl/overlay/list/main.go @@ -39,16 +39,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { files := util.FindFiles(path) wwlog.Debug("Iterating overlay path: %s", path) - if ListContents { - var fileCount int - for file := range files { - fmt.Printf("%-30s /%-12s\n", name, files[file]) - fileCount++ - } - if fileCount == 0 { - fmt.Printf("%-30s %-12d\n", name, 0) - } - } else if ListLong { + if ListLong { for file := range files { s, err := os.Stat(files[file]) if err != nil { @@ -61,7 +52,15 @@ func CobraRunE(cmd *cobra.Command, args []string) error { sys := s.Sys() fmt.Printf("%v %5d %-5d %-18s /%s\n", perms, sys.(*syscall.Stat_t).Uid, sys.(*syscall.Stat_t).Gid, overlays[o], files[file]) - + } + } else if ListContents { + var fileCount int + for file := range files { + fmt.Printf("%-30s /%-12s\n", name, files[file]) + fileCount++ + } + if fileCount == 0 { + fmt.Printf("%-30s %-12d\n", name, 0) } } else { fmt.Printf("%-30s %-12d\n", name, len(files)) diff --git a/internal/app/wwctl/overlay/show/main.go b/internal/app/wwctl/overlay/show/main.go index 9e132fc5..d1a27958 100644 --- a/internal/app/wwctl/overlay/show/main.go +++ b/internal/app/wwctl/overlay/show/main.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "fmt" - "io/ioutil" "os" "path" "path/filepath" @@ -38,7 +37,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { } if NodeName == "" { - f, err := ioutil.ReadFile(overlayFile) + f, err := os.ReadFile(overlayFile) if err != nil { wwlog.Error("Could not read file: %s", err) os.Exit(1) diff --git a/internal/app/wwctl/profile/edit/main.go b/internal/app/wwctl/profile/edit/main.go index a5694e5d..97d5a3ad 100644 --- a/internal/app/wwctl/profile/edit/main.go +++ b/internal/app/wwctl/profile/edit/main.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "fmt" "io" - "io/ioutil" "os" "strings" @@ -39,7 +38,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { profileMap := make(map[string]*node.NodeConf) // got proper yaml back _ = yaml.Unmarshal([]byte(profileListMsg.NodeConfMapYaml), profileMap) - file, err := ioutil.TempFile("/tmp", "ww4ProfileEdit*.yaml") + file, err := os.CreateTemp(os.TempDir(), "ww4ProfileEdit*.yaml") if err != nil { wwlog.Error("Could not create temp file:%s \n", err) } diff --git a/internal/app/wwctl/profile/list/main.go b/internal/app/wwctl/profile/list/main.go index 124d42a7..516da680 100644 --- a/internal/app/wwctl/profile/list/main.go +++ b/internal/app/wwctl/profile/list/main.go @@ -2,80 +2,24 @@ package list import ( "fmt" - "os" - "strings" - "github.com/hpcng/warewulf/internal/pkg/node" - "github.com/hpcng/warewulf/internal/pkg/wwlog" + apiprofile "github.com/hpcng/warewulf/internal/pkg/api/profile" + + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" "github.com/spf13/cobra" ) -func CobraRunE(cmd *cobra.Command, args []string) error { - - nodeDB, err := node.New() +func CobraRunE(cmd *cobra.Command, args []string) (err error) { + req := wwapiv1.GetProfileList{ + ShowAll: ShowAll, + Profiles: args, + } + profileInfo, err := apiprofile.ProfileList(&req) if err != nil { - wwlog.Error("Could not open node configuration: %s", err) - os.Exit(1) + return } - - profiles, err := nodeDB.FindAllProfiles() - if err != nil { - wwlog.Error("Could not find all nodes: %s", err) - os.Exit(1) + for _, str := range profileInfo.Output { + fmt.Printf("%s\n", str) } - - if ShowAll { - for _, profile := range node.FilterByName(profiles, args) { - fmt.Printf("################################################################################\n") - fmt.Printf("%-20s %-18s %s\n", "PROFILE NAME", "FIELD", "VALUE") - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Id", profile.Id.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Comment", profile.Comment.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Cluster", profile.ClusterName.Print()) - - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Discoverable", profile.Discoverable.PrintB()) - - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Container", profile.ContainerName.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "KernelOverride", profile.Kernel.Override.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "KernelArgs", profile.Kernel.Args.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Init", profile.Init.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Root", profile.Root.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "AssetKey", profile.AssetKey.Print()) - - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "SystemOverlay", profile.SystemOverlay.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "RuntimeOverlay", profile.RuntimeOverlay.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Ipxe", profile.Ipxe.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "IpmiNetmask", profile.Ipmi.Netmask.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "IpmiPort", profile.Ipmi.Port.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "IpmiGateway", profile.Ipmi.Gateway.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "IpmiUserName", profile.Ipmi.UserName.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "IpmiInterface", profile.Ipmi.Interface.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "IpmiWrite", profile.Ipmi.Write.PrintB()) - - for keyname, key := range profile.Tags { - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Tag["+keyname+"]", key.Print()) - } - - for name, netdev := range profile.NetDevs { - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), name+":IPADDR", netdev.Ipaddr.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), name+":NETMASK", netdev.Netmask.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), name+":GATEWAY", netdev.Gateway.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), name+":HWADDR", netdev.Hwaddr.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), name+":TYPE", netdev.Hwaddr.Print()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), name+":ONBOOT", netdev.OnBoot.PrintB()) - fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), name+":PRIMARY", netdev.Primary.PrintB()) - for keyname, key := range netdev.Tags { - fmt.Printf("%-20s %-18s %-12s %s\n", profile.Id.Get(), name+":TAG["+keyname+"]", key.Source(), key.Print()) - } - } - } - } else { - fmt.Printf("%-20s %s\n", "PROFILE NAME", "COMMENT/DESCRIPTION") - fmt.Printf(strings.Repeat("=", 80) + "\n") - - for _, profile := range node.FilterByName(profiles, args) { - fmt.Printf("%-20s %s\n", profile.Id.Print(), profile.Comment.Print()) - } - } - - return nil + return } diff --git a/internal/app/wwctl/root.go b/internal/app/wwctl/root.go index 81fe2a26..cc8927d6 100644 --- a/internal/app/wwctl/root.go +++ b/internal/app/wwctl/root.go @@ -31,12 +31,14 @@ var ( } verboseArg bool DebugFlag bool + LogLevel int ) func init() { rootCmd.PersistentFlags().BoolVarP(&verboseArg, "verbose", "v", false, "Run with increased verbosity.") rootCmd.PersistentFlags().BoolVarP(&DebugFlag, "debug", "d", false, "Run with debugging messages enabled.") - + rootCmd.PersistentFlags().IntVar(&LogLevel, "loglevel", wwlog.INFO, "Set log level to given string") + _ = rootCmd.PersistentFlags().MarkHidden("loglevel") rootCmd.SetUsageTemplate(help.UsageTemplate) rootCmd.SetHelpTemplate(help.HelpTemplate) @@ -65,6 +67,9 @@ func rootPersistentPreRunE(cmd *cobra.Command, args []string) error { } else { wwlog.SetLogLevel(wwlog.INFO) } + if LogLevel != wwlog.INFO { + wwlog.SetLogLevel(LogLevel) + } return nil } diff --git a/internal/pkg/api/apiconfig/client.go b/internal/pkg/api/apiconfig/client.go index df397975..595409e8 100644 --- a/internal/pkg/api/apiconfig/client.go +++ b/internal/pkg/api/apiconfig/client.go @@ -1,9 +1,10 @@ package apiconfig import ( - "gopkg.in/yaml.v2" - "io/ioutil" "log" + "os" + + "gopkg.in/yaml.v2" ) // ClientApiConfig contains configuration parameters for an API server. @@ -26,7 +27,7 @@ func NewClient(configFilePath string) (config ClientConfig, err error) { log.Printf("Loading api client configuration from: %v\n", configFilePath) var fileBytes []byte - fileBytes, err = ioutil.ReadFile(configFilePath) + fileBytes, err = os.ReadFile(configFilePath) if err != nil { return } diff --git a/internal/pkg/api/apiconfig/client_server.go b/internal/pkg/api/apiconfig/client_server.go index 89e035ca..c227e224 100644 --- a/internal/pkg/api/apiconfig/client_server.go +++ b/internal/pkg/api/apiconfig/client_server.go @@ -1,9 +1,10 @@ package apiconfig import ( - "gopkg.in/yaml.v2" - "io/ioutil" "log" + "os" + + "gopkg.in/yaml.v2" ) // ClientServerConfig is the full client server configuration. @@ -22,7 +23,7 @@ func NewClientServer(configFilePath string) (config ClientServerConfig, err erro log.Printf("Loading api client server configuration from: %v\n", configFilePath) var fileBytes []byte - fileBytes, err = ioutil.ReadFile(configFilePath) + fileBytes, err = os.ReadFile(configFilePath) if err != nil { return } diff --git a/internal/pkg/api/apiconfig/container/container.go b/internal/pkg/api/apiconfig/container/container.go index 8bc2ba23..beb29544 100644 --- a/internal/pkg/api/apiconfig/container/container.go +++ b/internal/pkg/api/apiconfig/container/container.go @@ -192,12 +192,6 @@ func ContainerImport(cip *wwapiv1.ContainerImportParameter) (containerName strin return } - fmt.Printf("Updating the container's /etc/resolv.conf\n") - err = util.CopyFile("/etc/resolv.conf", path.Join(container.RootFsDir(cip.Name), "/etc/resolv.conf")) - if err != nil { - wwlog.Warn("Could not copy /etc/resolv.conf into container: %s", err) - } - fmt.Printf("Building container: %s\n", cip.Name) err = container.Build(cip.Name, true) if err != nil { diff --git a/internal/pkg/api/apiconfig/server.go b/internal/pkg/api/apiconfig/server.go index 80f7006c..85be2bce 100644 --- a/internal/pkg/api/apiconfig/server.go +++ b/internal/pkg/api/apiconfig/server.go @@ -1,9 +1,10 @@ package apiconfig import ( - "gopkg.in/yaml.v2" - "io/ioutil" "log" + "os" + + "gopkg.in/yaml.v2" ) // ServerApiConfig contains configuration parameters for an API server. @@ -28,7 +29,7 @@ func NewServer(configFilePath string) (config ServerConfig, err error) { log.Printf("Loading api server configuration from: %v\n", configFilePath) var fileBytes []byte - fileBytes, err = ioutil.ReadFile(configFilePath) + fileBytes, err = os.ReadFile(configFilePath) if err != nil { return } diff --git a/internal/pkg/api/container/container.go b/internal/pkg/api/container/container.go index 757ea873..fdf720fd 100644 --- a/internal/pkg/api/container/container.go +++ b/internal/pkg/api/container/container.go @@ -197,12 +197,6 @@ func ContainerImport(cip *wwapiv1.ContainerImportParameter) (containerName strin return } - wwlog.Info("Updating the container's /etc/resolv.conf") - err = util.CopyFile("/etc/resolv.conf", path.Join(container.RootFsDir(cip.Name), "/etc/resolv.conf")) - if err != nil { - wwlog.Warn("Could not copy /etc/resolv.conf into container: %s", err) - } - err = container.SyncUids(cip.Name, !cip.SyncUser) if err != nil && !cip.SyncUser { err = fmt.Errorf("error in user sync, fix error and run 'syncuser' manually: %s", err) @@ -342,12 +336,15 @@ func ContainerShow(csp *wwapiv1.ContainerShowParameter) (response *wwapiv1.Conta containerName := csp.ContainerName if !container.ValidName(containerName) { - err = fmt.Errorf("%s is not a valid container", containerName) + err = fmt.Errorf("%s is not a valid container name", containerName) return } rootFsDir := container.RootFsDir(containerName) - + if !util.IsDir(rootFsDir) { + err = fmt.Errorf("%s is not a valid container", containerName) + return + } kernelVersion := container.KernelVersion(containerName) nodeDB, err := node.New() diff --git a/internal/pkg/api/node/list.go b/internal/pkg/api/node/list.go index f98293c8..330aa7b9 100644 --- a/internal/pkg/api/node/list.go +++ b/internal/pkg/api/node/list.go @@ -36,6 +36,7 @@ func NodeList(nodeGet *wwapiv1.GetNodeList) (nodeList wwapiv1.NodeList, err erro for k := range n.NetDevs { netNames = append(netNames, k) } + sort.Strings(netNames) nodeList.Output = append(nodeList.Output, fmt.Sprintf("%-22s %-26s %s", n.Id.Print(), n.Profiles.Print(), strings.Join(netNames, ", "))) } diff --git a/internal/pkg/api/profile/list.go b/internal/pkg/api/profile/list.go new file mode 100644 index 00000000..3044b3b6 --- /dev/null +++ b/internal/pkg/api/profile/list.go @@ -0,0 +1,140 @@ +package apiprofile + +import ( + "fmt" + "reflect" + "sort" + "strings" + + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/wwlog" +) + +/* +Returns the formatted list of profiles as string +*/ +func ProfileList(ShowOpt *wwapiv1.GetProfileList) (profileList wwapiv1.ProfileList, err error) { + profileList.Output = []string{} + nodeDB, err := node.New() + if err != nil { + wwlog.Error("Could not open node configuration: %s", err) + return + } + + profiles, err := nodeDB.FindAllProfiles() + if err != nil { + wwlog.Error("Could not find all profiles: %s", err) + return + } + profiles = node.FilterByName(profiles, ShowOpt.Profiles) + sort.Slice(profiles, func(i, j int) bool { + return profiles[i].Id.Get() < profiles[j].Id.Get() + }) + if ShowOpt.ShowAll { + for _, p := range profiles { + profileList.Output = append(profileList.Output, + fmt.Sprintf("%-20s %-18s %-12s %s", "PROFILE", "FIELD", "PROFILE", "VALUE"), strings.Repeat("=", 85)) + nType := reflect.TypeOf(p) + nVal := reflect.ValueOf(p) + nConfType := reflect.TypeOf(node.NodeConf{}) + for i := 0; i < nType.NumField(); i++ { + var fieldName, fieldSource, fieldVal string + nConfField, ok := nConfType.FieldByName(nType.Field(i).Name) + if ok { + fieldName = nConfField.Tag.Get("lopt") + } else { + fieldName = nType.Field(i).Name + } + if nType.Field(i).Type == reflect.TypeOf(node.Entry{}) { + entr := nVal.Field(i).Interface().(node.Entry) + fieldSource = entr.Source() + fieldVal = entr.Print() + profileList.Output = append(profileList.Output, + fmt.Sprintf("%-20s %-18s %-12s %s", p.Id.Print(), fieldName, fieldSource, fieldVal)) + } else if nType.Field(i).Type == reflect.TypeOf(map[string]*node.Entry{}) { + entrMap := nVal.Field(i).Interface().(map[string]*node.Entry) + for key, val := range entrMap { + profileList.Output = append(profileList.Output, + fmt.Sprintf("%-20s %-18s %-12s %s", p.Id.Print(), key, val.Source(), val.Print())) + } + } else if nType.Field(i).Type == reflect.TypeOf(map[string]*node.NetDevEntry{}) { + netDevs := nVal.Field(i).Interface().(map[string]*node.NetDevEntry) + for netName, netWork := range netDevs { + netInfoType := reflect.TypeOf(*netWork) + netInfoVal := reflect.ValueOf(*netWork) + netConfType := reflect.TypeOf(node.NetDevs{}) + for j := 0; j < netInfoType.NumField(); j++ { + netConfField, ok := netConfType.FieldByName(netInfoType.Field(j).Name) + if ok { + if netConfField.Tag.Get("lopt") != "nettagadd" { + fieldName = netName + ":" + netConfField.Tag.Get("lopt") + } else { + fieldName = netName + ":tag" + } + } else { + fieldName = netName + ":" + netInfoType.Field(j).Name + } + if netInfoType.Field(j).Type == reflect.TypeOf(node.Entry{}) { + entr := netInfoVal.Field(j).Interface().(node.Entry) + fieldSource = entr.Source() + fieldVal = entr.Print() + // only print fields with lopt + if netConfField.Tag.Get("lopt") != "" { + profileList.Output = append(profileList.Output, + fmt.Sprintf("%-20s %-18s %-12s %s", p.Id.Print(), fieldName, fieldSource, fieldVal)) + } + } else if netInfoType.Field(j).Type == reflect.TypeOf(map[string]*node.Entry{}) { + for key, val := range netInfoVal.Field(j).Interface().(map[string]*node.Entry) { + keyfieldName := fieldName + ":" + key + fieldSource = val.Source() + fieldVal = val.Print() + profileList.Output = append(profileList.Output, + fmt.Sprintf("%-20s %-18s %-12s %s", p.Id.Print(), keyfieldName, fieldSource, fieldVal)) + } + } + + } + } + } else if nType.Field(i).Type.Kind() == reflect.Ptr { + nestInfoType := reflect.TypeOf(nVal.Field(i).Interface()) + nestInfoVal := reflect.ValueOf(nVal.Field(i).Interface()) + // nestConfType := reflect.TypeOf(nConfField.Type.Elem().FieldByName()) + for j := 0; j < nestInfoType.Elem().NumField(); j++ { + nestConfField, ok := nConfField.Type.Elem().FieldByName(nestInfoType.Elem().Field(j).Name) + if ok { + fieldName = nestConfField.Tag.Get("lopt") + } else { + fieldName = nestInfoType.Elem().Field(j).Name + } + if nestInfoType.Elem().Field(j).Type == reflect.TypeOf(node.Entry{}) { + entr := nestInfoVal.Elem().Field(j).Interface().(node.Entry) + fieldSource = entr.Source() + fieldVal = entr.Print() + profileList.Output = append(profileList.Output, + fmt.Sprintf("%-20s %-18s %-12s %s", p.Id.Print(), fieldName, fieldSource, fieldVal)) + } else if nestInfoType.Elem().Field(j).Type == reflect.TypeOf(map[string]*node.Entry{}) { + for key, val := range nestInfoVal.Elem().Field(j).Interface().(map[string]*node.Entry) { + fieldName = fieldName + ":" + key + fieldSource = val.Source() + fieldVal = val.Print() + profileList.Output = append(profileList.Output, + fmt.Sprintf("%-20s %-18s %-12s %s", p.Id.Print(), fieldName, fieldSource, fieldVal)) + } + } + } + } + } + } + } else { + profileList.Output = append(profileList.Output, + fmt.Sprintf("%-20s %s", "PROFILE NAME", "COMMENT/DESCRIPTION")) + profileList.Output = append(profileList.Output, strings.Repeat("=", 80)) + + for _, profile := range profiles { + profileList.Output = append(profileList.Output, + fmt.Sprintf("%-20s %s", profile.Id.Print(), profile.Comment.Print())) + } + } + return +} diff --git a/internal/pkg/api/routes/v1/routes.proto b/internal/pkg/api/routes/v1/routes.proto index 6a51dc05..0027875a 100644 --- a/internal/pkg/api/routes/v1/routes.proto +++ b/internal/pkg/api/routes/v1/routes.proto @@ -117,10 +117,21 @@ message GetNodeList { repeated string Nodes = 8; } +// Get the formated output as string message NodeList { repeated string Output = 1; } +// Request a profile list view +message GetProfileList { + bool ShowAll = 1; + repeated string Profiles = 2; +} +// Get the formated output as string +message ProfileList { + repeated string Output = 1; +} + // NodeAddParameter contains all input for adding a node to be managed by // Warewulf. message NodeAddParameter { diff --git a/internal/pkg/api/routes/wwapiv1/routes.pb.go b/internal/pkg/api/routes/wwapiv1/routes.pb.go index fa6e9550..242bf312 100644 --- a/internal/pkg/api/routes/wwapiv1/routes.pb.go +++ b/internal/pkg/api/routes/wwapiv1/routes.pb.go @@ -946,6 +946,7 @@ func (x *GetNodeList) GetNodes() []string { return nil } +// Get the formated output as string type NodeList struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -993,6 +994,110 @@ func (x *NodeList) GetOutput() []string { return nil } +// Request a profile list view +type GetProfileList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ShowAll bool `protobuf:"varint,1,opt,name=ShowAll,proto3" json:"ShowAll,omitempty"` + Profiles []string `protobuf:"bytes,2,rep,name=Profiles,proto3" json:"Profiles,omitempty"` +} + +func (x *GetProfileList) Reset() { + *x = GetProfileList{} + if protoimpl.UnsafeEnabled { + mi := &file_routes_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetProfileList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProfileList) ProtoMessage() {} + +func (x *GetProfileList) ProtoReflect() protoreflect.Message { + mi := &file_routes_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProfileList.ProtoReflect.Descriptor instead. +func (*GetProfileList) Descriptor() ([]byte, []int) { + return file_routes_proto_rawDescGZIP(), []int{15} +} + +func (x *GetProfileList) GetShowAll() bool { + if x != nil { + return x.ShowAll + } + return false +} + +func (x *GetProfileList) GetProfiles() []string { + if x != nil { + return x.Profiles + } + return nil +} + +// Get the formated output as string +type ProfileList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Output []string `protobuf:"bytes,1,rep,name=Output,proto3" json:"Output,omitempty"` +} + +func (x *ProfileList) Reset() { + *x = ProfileList{} + if protoimpl.UnsafeEnabled { + mi := &file_routes_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProfileList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProfileList) ProtoMessage() {} + +func (x *ProfileList) ProtoReflect() protoreflect.Message { + mi := &file_routes_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProfileList.ProtoReflect.Descriptor instead. +func (*ProfileList) Descriptor() ([]byte, []int) { + return file_routes_proto_rawDescGZIP(), []int{16} +} + +func (x *ProfileList) GetOutput() []string { + if x != nil { + return x.Output + } + return nil +} + // NodeAddParameter contains all input for adding a node to be managed by // Warewulf. type NodeAddParameter struct { @@ -1007,7 +1112,7 @@ type NodeAddParameter struct { func (x *NodeAddParameter) Reset() { *x = NodeAddParameter{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[15] + mi := &file_routes_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1020,7 +1125,7 @@ func (x *NodeAddParameter) String() string { func (*NodeAddParameter) ProtoMessage() {} func (x *NodeAddParameter) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[15] + mi := &file_routes_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1033,7 +1138,7 @@ func (x *NodeAddParameter) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeAddParameter.ProtoReflect.Descriptor instead. func (*NodeAddParameter) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{15} + return file_routes_proto_rawDescGZIP(), []int{17} } func (x *NodeAddParameter) GetNodeConfYaml() string { @@ -1063,7 +1168,7 @@ type NodeYaml struct { func (x *NodeYaml) Reset() { *x = NodeYaml{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[16] + mi := &file_routes_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1076,7 +1181,7 @@ func (x *NodeYaml) String() string { func (*NodeYaml) ProtoMessage() {} func (x *NodeYaml) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[16] + mi := &file_routes_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1089,7 +1194,7 @@ func (x *NodeYaml) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeYaml.ProtoReflect.Descriptor instead. func (*NodeYaml) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{16} + return file_routes_proto_rawDescGZIP(), []int{18} } func (x *NodeYaml) GetNodeConfMapYaml() string { @@ -1113,7 +1218,7 @@ type NodeDeleteParameter struct { func (x *NodeDeleteParameter) Reset() { *x = NodeDeleteParameter{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[17] + mi := &file_routes_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1126,7 +1231,7 @@ func (x *NodeDeleteParameter) String() string { func (*NodeDeleteParameter) ProtoMessage() {} func (x *NodeDeleteParameter) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[17] + mi := &file_routes_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1139,7 +1244,7 @@ func (x *NodeDeleteParameter) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeDeleteParameter.ProtoReflect.Descriptor instead. func (*NodeDeleteParameter) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{17} + return file_routes_proto_rawDescGZIP(), []int{19} } func (x *NodeDeleteParameter) GetForce() bool { @@ -1174,7 +1279,7 @@ type NodeSetParameter struct { func (x *NodeSetParameter) Reset() { *x = NodeSetParameter{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[18] + mi := &file_routes_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1187,7 +1292,7 @@ func (x *NodeSetParameter) String() string { func (*NodeSetParameter) ProtoMessage() {} func (x *NodeSetParameter) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[18] + mi := &file_routes_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1200,7 +1305,7 @@ func (x *NodeSetParameter) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeSetParameter.ProtoReflect.Descriptor instead. func (*NodeSetParameter) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{18} + return file_routes_proto_rawDescGZIP(), []int{20} } func (x *NodeSetParameter) GetNodeConfYaml() string { @@ -1261,7 +1366,7 @@ type NodeStatus struct { func (x *NodeStatus) Reset() { *x = NodeStatus{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[19] + mi := &file_routes_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1274,7 +1379,7 @@ func (x *NodeStatus) String() string { func (*NodeStatus) ProtoMessage() {} func (x *NodeStatus) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[19] + mi := &file_routes_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1287,7 +1392,7 @@ func (x *NodeStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeStatus.ProtoReflect.Descriptor instead. func (*NodeStatus) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{19} + return file_routes_proto_rawDescGZIP(), []int{21} } func (x *NodeStatus) GetNodeName() string { @@ -1337,7 +1442,7 @@ type NodeStatusResponse struct { func (x *NodeStatusResponse) Reset() { *x = NodeStatusResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[20] + mi := &file_routes_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1350,7 +1455,7 @@ func (x *NodeStatusResponse) String() string { func (*NodeStatusResponse) ProtoMessage() {} func (x *NodeStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[20] + mi := &file_routes_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1363,7 +1468,7 @@ func (x *NodeStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeStatusResponse.ProtoReflect.Descriptor instead. func (*NodeStatusResponse) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{20} + return file_routes_proto_rawDescGZIP(), []int{22} } func (x *NodeStatusResponse) GetNodeStatus() []*NodeStatus { @@ -1387,7 +1492,7 @@ type VersionResponse struct { func (x *VersionResponse) Reset() { *x = VersionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[21] + mi := &file_routes_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1400,7 +1505,7 @@ func (x *VersionResponse) String() string { func (*VersionResponse) ProtoMessage() {} func (x *VersionResponse) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[21] + mi := &file_routes_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1413,7 +1518,7 @@ func (x *VersionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VersionResponse.ProtoReflect.Descriptor instead. func (*VersionResponse) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{21} + return file_routes_proto_rawDescGZIP(), []int{23} } func (x *VersionResponse) GetApiPrefix() string { @@ -1449,7 +1554,7 @@ type CanWriteConfig struct { func (x *CanWriteConfig) Reset() { *x = CanWriteConfig{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[22] + mi := &file_routes_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1462,7 +1567,7 @@ func (x *CanWriteConfig) String() string { func (*CanWriteConfig) ProtoMessage() {} func (x *CanWriteConfig) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[22] + mi := &file_routes_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1475,7 +1580,7 @@ func (x *CanWriteConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use CanWriteConfig.ProtoReflect.Descriptor instead. func (*CanWriteConfig) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{22} + return file_routes_proto_rawDescGZIP(), []int{24} } func (x *CanWriteConfig) GetCanWriteConfig() bool { @@ -1625,127 +1730,134 @@ var file_routes_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x67, 0x10, 0x03, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x6c, 0x6c, 0x10, 0x04, 0x22, 0x22, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x4f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x22, 0x54, 0x0a, 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, - 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, - 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, - 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, - 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x34, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, - 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x28, 0x0a, 0x0f, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, - 0x4d, 0x61, 0x70, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6e, - 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x4d, 0x61, 0x70, 0x59, 0x61, 0x6d, 0x6c, 0x22, 0x49, - 0x0a, 0x13, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, - 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, - 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xc8, 0x01, 0x0a, 0x10, 0x4e, 0x6f, - 0x64, 0x65, 0x53, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x22, - 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, - 0x6d, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x73, - 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x73, - 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x73, 0x18, 0x27, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x73, 0x22, 0x86, 0x01, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x70, 0x61, - 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x70, 0x61, 0x64, 0x64, - 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, 0x22, 0x4a, 0x0a, - 0x12, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x6e, - 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x79, 0x0a, 0x0f, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, - 0x61, 0x70, 0x69, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x61, 0x70, 0x69, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, - 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x77, 0x61, - 0x72, 0x65, 0x77, 0x75, 0x6c, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x72, 0x65, 0x77, 0x75, 0x6c, 0x66, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x38, 0x0a, 0x0e, 0x43, 0x61, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x61, 0x6e, 0x57, 0x72, 0x69, - 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, - 0x63, 0x61, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xa6, - 0x08, 0x0a, 0x05, 0x57, 0x57, 0x41, 0x70, 0x69, 0x12, 0x73, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x21, 0x2e, 0x77, 0x77, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x42, - 0x75, 0x69, 0x6c, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, - 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x3a, 0x01, 0x2a, 0x12, 0x64, 0x0a, - 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x12, 0x22, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x65, 0x74, 0x65, 0x72, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x15, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x0f, 0x2a, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x12, 0x70, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x5f, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f, - 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x6d, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x53, 0x68, 0x6f, 0x77, 0x12, 0x20, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x68, 0x6f, 0x77, + 0x75, 0x74, 0x22, 0x46, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x6c, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x6c, 0x6c, 0x12, 0x1a, + 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x08, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x25, 0x0a, 0x0b, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x22, 0x54, 0x0a, 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x50, 0x61, 0x72, 0x61, + 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, + 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, 0x64, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, + 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, + 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x34, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x59, + 0x61, 0x6d, 0x6c, 0x12, 0x28, 0x0a, 0x0f, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x4d, + 0x61, 0x70, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6e, 0x6f, + 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x4d, 0x61, 0x70, 0x59, 0x61, 0x6d, 0x6c, 0x22, 0x49, 0x0a, + 0x13, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, + 0x65, 0x74, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, + 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, + 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xc8, 0x01, 0x0a, 0x10, 0x4e, 0x6f, 0x64, + 0x65, 0x53, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, + 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, + 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, + 0x22, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, + 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x18, + 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, + 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, + 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, + 0x65, 0x73, 0x18, 0x27, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x73, 0x22, 0x86, 0x01, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, + 0x74, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x70, 0x61, 0x64, + 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x70, 0x61, 0x64, 0x64, 0x72, + 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, 0x22, 0x4a, 0x0a, 0x12, + 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x6e, 0x6f, + 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x79, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, + 0x70, 0x69, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x61, 0x70, 0x69, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, + 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x77, 0x61, 0x72, + 0x65, 0x77, 0x75, 0x6c, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x72, 0x65, 0x77, 0x75, 0x6c, 0x66, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x22, 0x38, 0x0a, 0x0e, 0x43, 0x61, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x61, 0x6e, 0x57, 0x72, 0x69, 0x74, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x63, + 0x61, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xa6, 0x08, + 0x0a, 0x05, 0x57, 0x57, 0x41, 0x70, 0x69, 0x12, 0x73, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x21, 0x2e, 0x77, 0x77, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x77, + 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x3a, 0x01, 0x2a, 0x12, 0x64, 0x0a, 0x0f, + 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, + 0x22, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x15, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x0f, 0x2a, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x12, 0x70, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x68, - 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x73, 0x68, 0x6f, 0x77, 0x12, 0x56, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, - 0x12, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, - 0x41, 0x64, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1a, 0x2e, 0x77, - 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, - 0x22, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x55, 0x0a, - 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x77, 0x77, - 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x2a, 0x08, 0x2f, 0x76, 0x31, 0x2f, - 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x4d, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, - 0x12, 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x1a, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, - 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6e, - 0x6f, 0x64, 0x65, 0x12, 0x59, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, 0x12, 0x1a, - 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, - 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1a, 0x2e, 0x77, 0x77, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x22, 0x0b, - 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x65, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x57, - 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x13, 0x2e, 0x77, - 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, - 0x73, 0x1a, 0x1c, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, - 0x65, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x4e, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x77, 0x77, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x29, 0x5a, 0x27, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x73, 0x2f, 0x77, 0x77, 0x61, 0x70, 0x69, 0x76, 0x31, 0x3b, 0x77, 0x77, 0x61, 0x70, 0x69, - 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x5f, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e, + 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x6d, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x53, 0x68, 0x6f, 0x77, 0x12, 0x20, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x68, 0x6f, 0x77, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x68, 0x6f, + 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x77, 0x12, 0x56, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x12, + 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, + 0x64, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1a, 0x2e, 0x77, 0x77, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x22, + 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x55, 0x0a, 0x0a, + 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x77, 0x77, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x2a, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6e, + 0x6f, 0x64, 0x65, 0x12, 0x4d, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, + 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x73, 0x1a, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, + 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, + 0x64, 0x65, 0x12, 0x59, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, 0x12, 0x1a, 0x2e, + 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x22, 0x0b, 0x2f, + 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x65, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x57, 0x0a, + 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x13, 0x2e, 0x77, 0x77, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, + 0x1a, 0x1c, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x4e, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x77, 0x77, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x29, 0x5a, 0x27, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x73, 0x2f, 0x77, 0x77, 0x61, 0x70, 0x69, 0x76, 0x31, 0x3b, 0x77, 0x77, 0x61, 0x70, 0x69, 0x76, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1761,7 +1873,7 @@ func file_routes_proto_rawDescGZIP() []byte { } var file_routes_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_routes_proto_msgTypes = make([]protoimpl.MessageInfo, 29) +var file_routes_proto_msgTypes = make([]protoimpl.MessageInfo, 31) var file_routes_proto_goTypes = []interface{}{ (GetNodeList_ListType)(0), // 0: wwapi.v1.GetNodeList.ListType (*ContainerBuildParameter)(nil), // 1: wwapi.v1.ContainerBuildParameter @@ -1779,33 +1891,35 @@ var file_routes_proto_goTypes = []interface{}{ (*NodeListResponse)(nil), // 13: wwapi.v1.NodeListResponse (*GetNodeList)(nil), // 14: wwapi.v1.GetNodeList (*NodeList)(nil), // 15: wwapi.v1.NodeList - (*NodeAddParameter)(nil), // 16: wwapi.v1.NodeAddParameter - (*NodeYaml)(nil), // 17: wwapi.v1.NodeYaml - (*NodeDeleteParameter)(nil), // 18: wwapi.v1.NodeDeleteParameter - (*NodeSetParameter)(nil), // 19: wwapi.v1.NodeSetParameter - (*NodeStatus)(nil), // 20: wwapi.v1.NodeStatus - (*NodeStatusResponse)(nil), // 21: wwapi.v1.NodeStatusResponse - (*VersionResponse)(nil), // 22: wwapi.v1.VersionResponse - (*CanWriteConfig)(nil), // 23: wwapi.v1.CanWriteConfig - nil, // 24: wwapi.v1.NetDev.FieldEntry - nil, // 25: wwapi.v1.NetDev.TagsEntry - nil, // 26: wwapi.v1.NodeInfo.FieldsEntry - nil, // 27: wwapi.v1.NodeInfo.NetDevsEntry - nil, // 28: wwapi.v1.NodeInfo.TagsEntry - nil, // 29: wwapi.v1.NodeInfo.KeysEntry - (*empty.Empty)(nil), // 30: google.protobuf.Empty + (*GetProfileList)(nil), // 16: wwapi.v1.GetProfileList + (*ProfileList)(nil), // 17: wwapi.v1.ProfileList + (*NodeAddParameter)(nil), // 18: wwapi.v1.NodeAddParameter + (*NodeYaml)(nil), // 19: wwapi.v1.NodeYaml + (*NodeDeleteParameter)(nil), // 20: wwapi.v1.NodeDeleteParameter + (*NodeSetParameter)(nil), // 21: wwapi.v1.NodeSetParameter + (*NodeStatus)(nil), // 22: wwapi.v1.NodeStatus + (*NodeStatusResponse)(nil), // 23: wwapi.v1.NodeStatusResponse + (*VersionResponse)(nil), // 24: wwapi.v1.VersionResponse + (*CanWriteConfig)(nil), // 25: wwapi.v1.CanWriteConfig + nil, // 26: wwapi.v1.NetDev.FieldEntry + nil, // 27: wwapi.v1.NetDev.TagsEntry + nil, // 28: wwapi.v1.NodeInfo.FieldsEntry + nil, // 29: wwapi.v1.NodeInfo.NetDevsEntry + nil, // 30: wwapi.v1.NodeInfo.TagsEntry + nil, // 31: wwapi.v1.NodeInfo.KeysEntry + (*empty.Empty)(nil), // 32: google.protobuf.Empty } var file_routes_proto_depIdxs = []int32{ 4, // 0: wwapi.v1.ContainerListResponse.containers:type_name -> wwapi.v1.ContainerInfo - 24, // 1: wwapi.v1.NetDev.Field:type_name -> wwapi.v1.NetDev.FieldEntry - 25, // 2: wwapi.v1.NetDev.Tags:type_name -> wwapi.v1.NetDev.TagsEntry - 26, // 3: wwapi.v1.NodeInfo.Fields:type_name -> wwapi.v1.NodeInfo.FieldsEntry - 27, // 4: wwapi.v1.NodeInfo.NetDevs:type_name -> wwapi.v1.NodeInfo.NetDevsEntry - 28, // 5: wwapi.v1.NodeInfo.Tags:type_name -> wwapi.v1.NodeInfo.TagsEntry - 29, // 6: wwapi.v1.NodeInfo.Keys:type_name -> wwapi.v1.NodeInfo.KeysEntry + 26, // 1: wwapi.v1.NetDev.Field:type_name -> wwapi.v1.NetDev.FieldEntry + 27, // 2: wwapi.v1.NetDev.Tags:type_name -> wwapi.v1.NetDev.TagsEntry + 28, // 3: wwapi.v1.NodeInfo.Fields:type_name -> wwapi.v1.NodeInfo.FieldsEntry + 29, // 4: wwapi.v1.NodeInfo.NetDevs:type_name -> wwapi.v1.NodeInfo.NetDevsEntry + 30, // 5: wwapi.v1.NodeInfo.Tags:type_name -> wwapi.v1.NodeInfo.TagsEntry + 31, // 6: wwapi.v1.NodeInfo.Keys:type_name -> wwapi.v1.NodeInfo.KeysEntry 12, // 7: wwapi.v1.NodeListResponse.nodes:type_name -> wwapi.v1.NodeInfo 0, // 8: wwapi.v1.GetNodeList.type:type_name -> wwapi.v1.GetNodeList.ListType - 20, // 9: wwapi.v1.NodeStatusResponse.nodeStatus:type_name -> wwapi.v1.NodeStatus + 22, // 9: wwapi.v1.NodeStatusResponse.nodeStatus:type_name -> wwapi.v1.NodeStatus 10, // 10: wwapi.v1.NetDev.FieldEntry.value:type_name -> wwapi.v1.NodeField 10, // 11: wwapi.v1.NetDev.TagsEntry.value:type_name -> wwapi.v1.NodeField 10, // 12: wwapi.v1.NodeInfo.FieldsEntry.value:type_name -> wwapi.v1.NodeField @@ -1815,25 +1929,25 @@ var file_routes_proto_depIdxs = []int32{ 1, // 16: wwapi.v1.WWApi.ContainerBuild:input_type -> wwapi.v1.ContainerBuildParameter 2, // 17: wwapi.v1.WWApi.ContainerDelete:input_type -> wwapi.v1.ContainerDeleteParameter 3, // 18: wwapi.v1.WWApi.ContainerImport:input_type -> wwapi.v1.ContainerImportParameter - 30, // 19: wwapi.v1.WWApi.ContainerList:input_type -> google.protobuf.Empty + 32, // 19: wwapi.v1.WWApi.ContainerList:input_type -> google.protobuf.Empty 6, // 20: wwapi.v1.WWApi.ContainerShow:input_type -> wwapi.v1.ContainerShowParameter - 16, // 21: wwapi.v1.WWApi.NodeAdd:input_type -> wwapi.v1.NodeAddParameter - 18, // 22: wwapi.v1.WWApi.NodeDelete:input_type -> wwapi.v1.NodeDeleteParameter + 18, // 21: wwapi.v1.WWApi.NodeAdd:input_type -> wwapi.v1.NodeAddParameter + 20, // 22: wwapi.v1.WWApi.NodeDelete:input_type -> wwapi.v1.NodeDeleteParameter 9, // 23: wwapi.v1.WWApi.NodeList:input_type -> wwapi.v1.NodeNames - 19, // 24: wwapi.v1.WWApi.NodeSet:input_type -> wwapi.v1.NodeSetParameter + 21, // 24: wwapi.v1.WWApi.NodeSet:input_type -> wwapi.v1.NodeSetParameter 9, // 25: wwapi.v1.WWApi.NodeStatus:input_type -> wwapi.v1.NodeNames - 30, // 26: wwapi.v1.WWApi.Version:input_type -> google.protobuf.Empty + 32, // 26: wwapi.v1.WWApi.Version:input_type -> google.protobuf.Empty 5, // 27: wwapi.v1.WWApi.ContainerBuild:output_type -> wwapi.v1.ContainerListResponse - 30, // 28: wwapi.v1.WWApi.ContainerDelete:output_type -> google.protobuf.Empty + 32, // 28: wwapi.v1.WWApi.ContainerDelete:output_type -> google.protobuf.Empty 5, // 29: wwapi.v1.WWApi.ContainerImport:output_type -> wwapi.v1.ContainerListResponse 5, // 30: wwapi.v1.WWApi.ContainerList:output_type -> wwapi.v1.ContainerListResponse 7, // 31: wwapi.v1.WWApi.ContainerShow:output_type -> wwapi.v1.ContainerShowResponse 13, // 32: wwapi.v1.WWApi.NodeAdd:output_type -> wwapi.v1.NodeListResponse - 30, // 33: wwapi.v1.WWApi.NodeDelete:output_type -> google.protobuf.Empty + 32, // 33: wwapi.v1.WWApi.NodeDelete:output_type -> google.protobuf.Empty 13, // 34: wwapi.v1.WWApi.NodeList:output_type -> wwapi.v1.NodeListResponse 13, // 35: wwapi.v1.WWApi.NodeSet:output_type -> wwapi.v1.NodeListResponse - 21, // 36: wwapi.v1.WWApi.NodeStatus:output_type -> wwapi.v1.NodeStatusResponse - 22, // 37: wwapi.v1.WWApi.Version:output_type -> wwapi.v1.VersionResponse + 23, // 36: wwapi.v1.WWApi.NodeStatus:output_type -> wwapi.v1.NodeStatusResponse + 24, // 37: wwapi.v1.WWApi.Version:output_type -> wwapi.v1.VersionResponse 27, // [27:38] is the sub-list for method output_type 16, // [16:27] is the sub-list for method input_type 16, // [16:16] is the sub-list for extension type_name @@ -2028,7 +2142,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeAddParameter); i { + switch v := v.(*GetProfileList); i { case 0: return &v.state case 1: @@ -2040,7 +2154,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeYaml); i { + switch v := v.(*ProfileList); i { case 0: return &v.state case 1: @@ -2052,7 +2166,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeDeleteParameter); i { + switch v := v.(*NodeAddParameter); i { case 0: return &v.state case 1: @@ -2064,7 +2178,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeSetParameter); i { + switch v := v.(*NodeYaml); i { case 0: return &v.state case 1: @@ -2076,7 +2190,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeStatus); i { + switch v := v.(*NodeDeleteParameter); i { case 0: return &v.state case 1: @@ -2088,7 +2202,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeStatusResponse); i { + switch v := v.(*NodeSetParameter); i { case 0: return &v.state case 1: @@ -2100,7 +2214,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VersionResponse); i { + switch v := v.(*NodeStatus); i { case 0: return &v.state case 1: @@ -2112,6 +2226,30 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeStatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routes_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VersionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routes_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CanWriteConfig); i { case 0: return &v.state @@ -2130,7 +2268,7 @@ func file_routes_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_routes_proto_rawDesc, NumEnums: 1, - NumMessages: 29, + NumMessages: 31, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/pkg/batch/batch.go b/internal/pkg/batch/batch.go index 1473828d..677851c4 100644 --- a/internal/pkg/batch/batch.go +++ b/internal/pkg/batch/batch.go @@ -17,13 +17,6 @@ func New(active int) *BatchPool { return pool } -func Min(x, y int) int { - if x < y { - return x - } - return y -} - func (pool *BatchPool) Submit(f func()) { pool.jobs = append(pool.jobs, f) } diff --git a/internal/pkg/batch/batch_test.go b/internal/pkg/batch/batch_test.go new file mode 100644 index 00000000..f3884776 --- /dev/null +++ b/internal/pkg/batch/batch_test.go @@ -0,0 +1,34 @@ +package batch + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +/* Submits 10 jobs into a pool that supports 2 simultaneous jobs, and + tests that only two of the jobs ran at a time by capturing the time + that they ran and comparing against the start time. */ +func TestBatchPool (t *testing.T) { + pool := New(2) + var times []time.Time + for i := 0; i <= 10; i++ { + pool.Submit(func() { + times = append(times, time.Now()) + time.Sleep(1 * time.Second) + }) + } + startTime := time.Now() + pool.Run() + assert.Equal(t, 0 * time.Second, times[0].Sub(startTime).Round(time.Second)) + assert.Equal(t, 0 * time.Second, times[1].Sub(startTime).Round(time.Second)) + assert.Equal(t, 1 * time.Second, times[2].Sub(startTime).Round(time.Second)) + assert.Equal(t, 1 * time.Second, times[3].Sub(startTime).Round(time.Second)) + assert.Equal(t, 2 * time.Second, times[4].Sub(startTime).Round(time.Second)) + assert.Equal(t, 2 * time.Second, times[5].Sub(startTime).Round(time.Second)) + assert.Equal(t, 3 * time.Second, times[6].Sub(startTime).Round(time.Second)) + assert.Equal(t, 3 * time.Second, times[7].Sub(startTime).Round(time.Second)) + assert.Equal(t, 4 * time.Second, times[8].Sub(startTime).Round(time.Second)) + assert.Equal(t, 4 * time.Second, times[9].Sub(startTime).Round(time.Second)) +} diff --git a/internal/pkg/buildconfig/defaults.go b/internal/pkg/buildconfig/defaults.go index 24cb2d40..f739cff1 100644 --- a/internal/pkg/buildconfig/defaults.go +++ b/internal/pkg/buildconfig/defaults.go @@ -19,6 +19,7 @@ var ( release string = "UNDEF" wwclientdir string = "UNDEF" datadir string = "UNDEF" + tmpdir string = "UNDEF" ) func BINDIR() string { @@ -27,7 +28,7 @@ func BINDIR() string { } func DATADIR() string { - wwlog.Debug("DATADIR = '%s'", bindir) + wwlog.Debug("DATADIR = '%s'", datadir) return datadir } @@ -90,3 +91,8 @@ func WWCLIENTDIR() string { wwlog.Debug("WWCLIENTDIR = '%s'", wwclientdir) return wwclientdir } + +func TMPDIR() string { + wwlog.Debug("TMPDIR = '%s'", tmpdir) + return tmpdir +} diff --git a/internal/pkg/buildconfig/setconfigs.go.in b/internal/pkg/buildconfig/setconfigs.go.in index e10303e3..f049ffeb 100644 --- a/internal/pkg/buildconfig/setconfigs.go.in +++ b/internal/pkg/buildconfig/setconfigs.go.in @@ -15,4 +15,5 @@ func init() { version = "@VERSION@" release = "@RELEASE@" wwclientdir = "@WWCLIENTDIR@" + tmpdir = "@TMPDIR@" } diff --git a/internal/pkg/configure/tftp.go b/internal/pkg/configure/tftp.go index d321f023..9e71f67a 100644 --- a/internal/pkg/configure/tftp.go +++ b/internal/pkg/configure/tftp.go @@ -11,9 +11,8 @@ import ( "github.com/hpcng/warewulf/internal/pkg/wwlog" ) -var tftpdir string = path.Join(buildconfig.TFTPDIR(), "warewulf") - func TFTP() error { + var tftpdir string = path.Join(buildconfig.TFTPDIR(), "warewulf") controller, err := warewulfconf.New() if err != nil { wwlog.Error("%s", err) @@ -27,11 +26,15 @@ func TFTP() error { } fmt.Printf("Writing PXE files to: %s\n", tftpdir) - for _, f := range [4]string{"x86_64.efi", "x86_64.kpxe", "arm64.efi"} { - err = util.SafeCopyFile(path.Join(buildconfig.DATADIR(), "warewulf", "ipxe", f), path.Join(tftpdir, f)) + copyCheck := make(map[string]bool) + for _, f := range controller.Tftp.IpxeBinaries { + if copyCheck[f] { + continue + } + copyCheck[f] = true + err = util.SafeCopyFile(path.Join(buildconfig.DATADIR(), f), path.Join(tftpdir, f)) if err != nil { - wwlog.Error("%s", err) - return err + wwlog.Warn("ipxe binary could not be copied, booting may not work: %s", err) } } @@ -39,7 +42,7 @@ func TFTP() error { wwlog.Info("Warewulf does not auto start TFTP services due to disable by warewulf.conf") os.Exit(0) } - + fmt.Printf("Enabling and restarting the TFTP services\n") err = util.SystemdStart(controller.Tftp.SystemdName) if err != nil { diff --git a/internal/pkg/container/mountpoints.go b/internal/pkg/container/mountpoints.go new file mode 100644 index 00000000..727ff35c --- /dev/null +++ b/internal/pkg/container/mountpoints.go @@ -0,0 +1,35 @@ +package container + +import ( + "strings" + + "github.com/hpcng/warewulf/internal/pkg/warewulfconf" + "github.com/hpcng/warewulf/internal/pkg/wwlog" +) + +/* +Create a slice iof MntDetails from a string slice with following +format "source:[:destination][:readonly]" if destination is not +given, the source is used as destination +*/ +func InitMountPnts(binds []string) (mounts []*warewulfconf.MountEntry) { + wwlog.Debug("Trying to mount following mount points: %s", mounts) + for _, b := range binds { + bind := strings.Split(b, ":") + dest := bind[0] + if len(bind) >= 2 { + dest = bind[1] + } + readonly := false + if len(bind) >= 3 && bind[2] == "ro" { + readonly = true + } + mntPnt := warewulfconf.MountEntry{ + Source: bind[0], + Dest: dest, + ReadOnly: readonly, + } + mounts = append(mounts, &mntPnt) + } + return mounts +} diff --git a/internal/pkg/container/syncuids.go b/internal/pkg/container/syncuids.go index e6f14394..62cd8b27 100644 --- a/internal/pkg/container/syncuids.go +++ b/internal/pkg/container/syncuids.go @@ -39,73 +39,67 @@ func SyncUids(containerName string, showOnly bool) error { var userDb []completeUserInfo passwdName := "/etc/passwd" groupName := "/etc/group" - fullPath := RootFsDir(containerName) - hostName, err := createPasswdMap(passwdName) + + // populate db with users from the host + hostUsers, err := createPasswdMap(passwdName) if err != nil { wwlog.Error("Could not open "+passwdName) return err } - // populate db with the user of the - for _, user := range hostName { - userDb = append(userDb, completeUserInfo{Name: user.name, - UidHost: user.uid, GidHost: user.gid, UidCont: -1, GidCont: -1}) + for _, hostUser := range hostUsers { + userDb = append(userDb, completeUserInfo{Name: hostUser.name, + UidHost: hostUser.uid, GidHost: hostUser.gid, UidCont: -1, GidCont: -1}) } - contName, err := createPasswdMap(path.Join(fullPath, passwdName)) + // merge container users into db and track users that are only in the + // container + fullPath := RootFsDir(containerName) + containerUsers, err := createPasswdMap(path.Join(fullPath, passwdName)) if err != nil { wwlog.Error("Could not open "+path.Join(fullPath, passwdName)) return err } var userOnlyCont []string - for _, userCont := range contName { + for _, containerUser := range containerUsers { foundUser := false - for idxHost, userHost := range userDb { - if userCont.name == userHost.Name { + for idxHost, user := range userDb { + if containerUser.name == user.Name { foundUser = true - (&userDb[idxHost]).UidCont = userCont.uid - (&userDb[idxHost]).GidCont = userCont.gid + (&userDb[idxHost]).UidCont = containerUser.uid + (&userDb[idxHost]).GidCont = containerUser.gid } } if !foundUser { - userDb = append(userDb, completeUserInfo{Name: userCont.name, - UidHost: -1, GidHost: -1, UidCont: userCont.uid, GidCont: userCont.gid}) - wwlog.Warn("user: %s:%v:%v not present on host", userCont.name, userCont.uid, userCont.gid) - userOnlyCont = append(userOnlyCont, userCont.name) + userDb = append(userDb, completeUserInfo{Name: containerUser.name, + UidHost: -1, GidHost: -1, UidCont: containerUser.uid, GidCont: containerUser.gid}) + wwlog.Warn("user: %s:%v:%v not present on host", containerUser.name, containerUser.uid, containerUser.gid) + userOnlyCont = append(userOnlyCont, containerUser.name) } - } - // find out which user/group are only in the container - for _, user := range userDb { - if user.UidHost == -1 { - for _, userCheck := range userDb { - if userCheck.UidHost == user.UidCont { - wwlog.Warn(fmt.Sprintf("uid(%v) collision for host: %s and container: %s", - user.UidCont, user.Name, userCheck.Name)) - return errors.New(fmt.Sprintf("user %s only present in container has same uid(%v) as user %s on host,\n"+ - "add this user to /etc/passwd on host", user.Name, user.UidCont, userCheck.Name)) - } + + // detect users in the host and container with conflicting uids + for _, containerUser := range userDb { + if (containerUser.UidCont == -1 || containerUser.UidHost != -1) { + // containerUser is either not actually in the + // container or is also in the host + continue + } + for _, hostUser := range userDb { + if hostUser.UidHost == containerUser.UidCont { + wwlog.Warn("uid(%v) collision for host: %s and container: %s", + containerUser.UidCont, hostUser.Name, containerUser.Name) + return errors.New(fmt.Sprintf("user %s only present in container has same uid(%v) as user %s on host,\n"+ + "add this user to /etc/passwd on host", containerUser.Name, containerUser.UidCont, hostUser.Name)) } } - /* Users can have same gid, disabling this code - if user.GidHost == -1 { - for _, userCheck := range userDb { - if userCheck.GidHost == user.GidCont { - wwlog.Warn(fmt.Sprintf("gid(%v) collision for host: %s and container: %s", - user.GidCont, user.Name, userCheck.Name)) - return errors.New(fmt.Sprintf("user %s only present in container has same gid(%v) as user %s on host,\n"+ - " add this group to /etc/group on host", user.Name, user.GidCont, userCheck.Name)) - } - } - } - */ - } + if showOnly { wwlog.Info("uid/gid not synced, run \nwwctl container syncuser --write %s\nto synchronize uid/gids.", containerName) return nil } - // create list of files which need changed ownerships in order to change them later what - // avoid uid/gid collisions + // create list of files which need changed ownerships in order to + // change them later what avoid uid/gid collisions for idx, user := range userDb { if (user.UidHost != user.UidCont && user.UidHost != -1) || (user.GidHost != user.GidCont && user.GidHost != -1 && user.UidHost != -1) { diff --git a/internal/pkg/container/util.go b/internal/pkg/container/util.go index 3d741a10..48211710 100644 --- a/internal/pkg/container/util.go +++ b/internal/pkg/container/util.go @@ -1,7 +1,6 @@ package container import ( - "io/ioutil" "os" "github.com/pkg/errors" @@ -27,7 +26,7 @@ func ListSources() ([]string, error) { } wwlog.Debug("Searching for VNFS Rootfs directories: %s", SourceParentDir()) - sources, err := ioutil.ReadDir(SourceParentDir()) + sources, err := os.ReadDir(SourceParentDir()) if err != nil { return ret, err } diff --git a/internal/pkg/kernel/kernel.go b/internal/pkg/kernel/kernel.go index 4fae2ea9..9983572e 100644 --- a/internal/pkg/kernel/kernel.go +++ b/internal/pkg/kernel/kernel.go @@ -4,7 +4,6 @@ import ( "compress/gzip" "fmt" "io" - "io/ioutil" "os" "path" "path/filepath" @@ -51,7 +50,7 @@ func GetKernelVersion(kernelName string) string { wwlog.Error("Kernel Name is not defined") return "" } - kernelVersion, err := ioutil.ReadFile(KernelVersionFile(kernelName)) + kernelVersion, err := os.ReadFile(KernelVersionFile(kernelName)) if err != nil { return "" } @@ -96,7 +95,7 @@ func ListKernels() ([]string, error) { wwlog.Debug("Searching for Kernel image directories: %s", KernelImageTopDir()) - kernels, err := ioutil.ReadDir(KernelImageTopDir()) + kernels, err := os.ReadDir(KernelImageTopDir()) if err != nil { return ret, err } @@ -198,7 +197,7 @@ func Build(kernelVersion, kernelName, root string) (string, error) { driversDestination, []string{ "." + kernelDriversRelative, - "./lib/firmware" }, + "./lib/firmware"}, []string{}, // ignore cross-device files true, diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go index 07f191a4..d69bdf74 100644 --- a/internal/pkg/node/constructors.go +++ b/internal/pkg/node/constructors.go @@ -2,7 +2,7 @@ package node import ( "errors" - "io/ioutil" + "os" "path" "sort" "strings" @@ -49,7 +49,7 @@ func New() (NodeYaml, error) { var ret NodeYaml wwlog.Verbose("Opening node configuration file: %s", ConfigFile) - data, err := ioutil.ReadFile(ConfigFile) + data, err := os.ReadFile(ConfigFile) if err != nil { return ret, err } @@ -79,8 +79,8 @@ func (config *NodeYaml) FindAllNodes() ([]NodeInfo, error) { } */ var defConf map[string]*NodeConf - wwlog.Verbose("Opening defaults from file %s\n", DefaultConfig) - defData, err := ioutil.ReadFile(DefaultConfig) + wwlog.Verbose("Opening defaults from file failed %s\n", DefaultConfig) + defData, err := os.ReadFile(DefaultConfig) if err != nil { wwlog.Verbose("Couldn't read DefaultConfig :%s\n", err) } @@ -134,13 +134,6 @@ func (config *NodeYaml) FindAllNodes() ([]NodeInfo, error) { for _, netdev := range n.NetDevs { netdev.SetDefFrom(defConfNet) } - // set default/primary network is just one network exist - if len(n.NetDevs) == 1 { - // only way to get the key - for key := range node.NetDevs { - n.NetDevs[key].Primary.SetB(true) - } - } // backward compatibility n.Ipmi.Ipaddr.Set(node.IpmiIpaddr) n.Ipmi.Netmask.Set(node.IpmiNetmask) @@ -178,6 +171,20 @@ func (config *NodeYaml) FindAllNodes() ([]NodeInfo, error) { wwlog.Verbose("Merging profile into node: %s <- %s", nodename, profileName) n.SetAltFrom(config.NodeProfiles[profileName], profileName) } + // set default/primary network is just one network exist + if len(n.NetDevs) >= 1 { + tmpNets := make([]string, 0, len(n.NetDevs)) + for key := range node.NetDevs { + tmpNets = append(tmpNets, key) + } + sort.Strings(tmpNets) + // if a value is present in profile or node, default is not visible + wwlog.Debug("%s setting primary network device: %s", n.Id.Get(), tmpNets[0]) + n.PrimaryNetDev.SetDefault(tmpNets[0]) + } + if dev, ok := n.NetDevs[n.PrimaryNetDev.Get()]; ok { + dev.Primary.SetDefaultB(true) + } ret = append(ret, n) } diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go index cf2fa48a..72bea85a 100644 --- a/internal/pkg/node/datastructure.go +++ b/internal/pkg/node/datastructure.go @@ -48,6 +48,7 @@ type NodeConf struct { Tags map[string]string `yaml:"tags,omitempty" lopt:"tagadd" comment:"base key"` TagsDel []string `yaml:"tagsdel,omitempty" lopt:"tagdel" comment:"remove this tags"` // should not go to disk only to wire Keys map[string]string `yaml:"keys,omitempty"` // Reverse compatibility + PrimaryNetDev string `yaml:"primary network,omitempty" lopt:"primarynet" sopt:"p" comment:"Set the primary network interface"` } type IpmiConf struct { @@ -80,8 +81,6 @@ type NetDevs struct { Netmask string `yaml:"netmask,omitempty" lopt:"netmask" sopt:"M" comment:"Set the networks netmask"` Gateway string `yaml:"gateway,omitempty" lopt:"gateway" sopt:"G" comment:"Set the node's network device gateway"` MTU string `yaml:"mtu,omitempty" lopt:"mtu" comment:"Set the mtu"` - Primary string `yaml:"primary,omitempty" lopt:"primary" comment:"Enable/disable network device as primary (yes/no)"` - Default string `yaml:"default,omitempty"` /* backward compatibility */ Tags map[string]string `yaml:"tags,omitempty" lopt:"nettagadd" comment:"network tags"` TagsDel []string `yaml:"tagsdel,omitempty" lopt:"nettagdel" comment:"delete network tags"` // should not go to disk only to wire } @@ -122,6 +121,7 @@ type NodeInfo struct { Kernel *KernelEntry Ipmi *IpmiEntry Profiles Entry + PrimaryNetDev Entry NetDevs map[string]*NetDevEntry Tags map[string]*Entry } diff --git a/internal/pkg/node/methods.go b/internal/pkg/node/methods.go index f404ebd2..4ee8deca 100644 --- a/internal/pkg/node/methods.go +++ b/internal/pkg/node/methods.go @@ -166,6 +166,17 @@ func (ent *Entry) SetDefaultSlice(val []string) { } +/* +Set default etry as bool +*/ +func (ent *Entry) SetDefaultB(val bool) { + if val { + ent.def = []string{"true"} + } else { + ent.def = []string{"false"} + } +} + /* Remove a element from a slice */ diff --git a/internal/pkg/node/transformers.go b/internal/pkg/node/transformers.go index 177d7d49..75960b25 100644 --- a/internal/pkg/node/transformers.go +++ b/internal/pkg/node/transformers.go @@ -28,7 +28,7 @@ func (nodeConf *NodeConf) GetFrom(nodeInfo NodeInfo) { } /* -Abstract function which populates a NodeConf form the given NodeInfo +Abstract function which populates a NodeConf from the given NodeInfo via getter functions. */ func (nodeConf *NodeConf) getterFrom(nodeInfo NodeInfo, diff --git a/internal/pkg/oci/puller.go b/internal/pkg/oci/puller.go index c071f2f4..97ed7d62 100644 --- a/internal/pkg/oci/puller.go +++ b/internal/pkg/oci/puller.go @@ -5,7 +5,6 @@ import ( "crypto/sha256" "encoding/json" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -140,7 +139,7 @@ func (p *puller) Pull(ctx context.Context, uri, dst string) (err error) { } // defaults to $TMPDIR or /tmp - tmpDir, err := ioutil.TempDir(p.tmpDirPath, "oci-bundle-") + tmpDir, err := os.MkdirTemp(p.tmpDirPath, "oci-bundle-") if err != nil { return err } diff --git a/internal/pkg/overlay/datastructure.go b/internal/pkg/overlay/datastructure.go index 2a427bee..eb8004cf 100644 --- a/internal/pkg/overlay/datastructure.go +++ b/internal/pkg/overlay/datastructure.go @@ -31,6 +31,7 @@ type TemplateStruct struct { Dhcp warewulfconf.DhcpConf Nfs warewulfconf.NfsConf Warewulf warewulfconf.WarewulfConf + Tftp warewulfconf.TftpConf AllNodes []node.NodeInfo node.NodeConf // backward compatiblity @@ -64,6 +65,7 @@ func InitStruct(nodeInfo node.NodeInfo) TemplateStruct { tstruct.AllNodes = allNodes tstruct.Nfs = *controller.Nfs tstruct.Dhcp = *controller.Dhcp + tstruct.Tftp = *controller.Tftp tstruct.Warewulf = *controller.Warewulf tstruct.Ipaddr = controller.Ipaddr tstruct.Ipaddr6 = controller.Ipaddr6 diff --git a/internal/pkg/overlay/funcmap.go b/internal/pkg/overlay/funcmap.go index 9ecf632f..49b50d3d 100644 --- a/internal/pkg/overlay/funcmap.go +++ b/internal/pkg/overlay/funcmap.go @@ -2,7 +2,6 @@ package overlay import ( "bufio" - "io/ioutil" "os" "path" "strings" @@ -23,7 +22,7 @@ func templateFileInclude(inc string) string { inc = path.Join(buildconfig.SYSCONFDIR(), "warewulf", inc) } wwlog.Debug("Including file into template: %s", inc) - content, err := ioutil.ReadFile(inc) + content, err := os.ReadFile(inc) if err != nil { wwlog.Verbose("Could not include file into template: %s", err) } @@ -93,7 +92,7 @@ func templateContainerFileInclude(containername string, filepath string) string return "" } - content, err := ioutil.ReadFile(path.Join(containerDir, filepath)) + content, err := os.ReadFile(path.Join(containerDir, filepath)) if err != nil { wwlog.Error("Template include failed: %s", err) diff --git a/internal/pkg/overlay/overlay.go b/internal/pkg/overlay/overlay.go index 66443f93..3754fa9c 100644 --- a/internal/pkg/overlay/overlay.go +++ b/internal/pkg/overlay/overlay.go @@ -5,7 +5,6 @@ import ( "bytes" "fmt" "io/fs" - "io/ioutil" "os" "path" "path/filepath" @@ -103,10 +102,9 @@ Get all overlays present in warewulf */ func FindOverlays() ([]string, error) { var ret []string - var files []os.FileInfo dotfilecheck, _ := regexp.Compile(`^\..*`) - files, err := ioutil.ReadDir(OverlaySourceTopDir()) + files, err := os.ReadDir(OverlaySourceTopDir()) if err != nil { return ret, errors.Wrap(err, "could not get list of overlays") } @@ -154,7 +152,7 @@ func BuildOverlay(nodeInfo node.NodeInfo, overlayNames []string) error { wwlog.Debug("Created directory for %s: %s", name, overlayImageDir) - buildDir, err := ioutil.TempDir(os.TempDir(), ".wwctl-overlay-") + buildDir, err := os.MkdirTemp(os.TempDir(), ".wwctl-overlay-") if err != nil { return errors.Wrapf(err, "Failed to create temporary directory for %s", name) } diff --git a/internal/pkg/pidfile/pidfile.go b/internal/pkg/pidfile/pidfile.go index 9541ea99..25565340 100644 --- a/internal/pkg/pidfile/pidfile.go +++ b/internal/pkg/pidfile/pidfile.go @@ -5,7 +5,6 @@ package pidfile import ( "errors" "fmt" - "io/ioutil" "os" "strconv" "strings" @@ -47,11 +46,11 @@ func WriteControl(filename string, pid int, overwrite bool) (int, error) { } // We're clear to (over)write the file - return pid, ioutil.WriteFile(filename, []byte(fmt.Sprintf("%d\n", pid)), 0644) + return pid, os.WriteFile(filename, []byte(fmt.Sprintf("%d\n", pid)), 0644) } func pidfileContents(filename string) (int, error) { - contents, err := ioutil.ReadFile(filename) + contents, err := os.ReadFile(filename) if err != nil { return 0, err } diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go index 3f97fbb5..6ad445cb 100644 --- a/internal/pkg/util/util.go +++ b/internal/pkg/util/util.go @@ -24,12 +24,16 @@ import ( // reserve some number of cpus for system/warwulfd usage var processLimitedReserve int = 4 + // maximum number of concurrent spawned processes -var processLimitedMax = MaxInt(1, runtime.NumCPU() - processLimitedReserve) +var processLimitedMax = MaxInt(1, runtime.NumCPU()-processLimitedReserve) + // Channel used as semaphore to specififed processLimitedMax var processLimitedChan = make(chan int, processLimitedMax) + // Current number of processes started + queued var processLimitedNum int32 = 0 + // Counter over total history of started processes var processLimitedCounter uint32 = 0 @@ -53,7 +57,7 @@ func ProcessLimitedStatus() (running int32, queued int32) { return } -func MaxInt( a int, b int ) int { +func MaxInt(a int, b int) int { if a > b { return a } @@ -65,7 +69,7 @@ func FirstError(errs ...error) (err error) { for _, e := range errs { if err == nil { err = e - }else if e != nil { + } else if e != nil { wwlog.ErrorExc(e, "Unhandled error") } } @@ -207,7 +211,7 @@ func ValidateOrDie(message string, pattern string, expr string) { } } -//****************************************************************************** +// ****************************************************************************** func FindFiles(path string) []string { var ret []string @@ -244,7 +248,7 @@ func FindFiles(path string) []string { return ret } -//****************************************************************************** +// ****************************************************************************** func FindFilterFiles( path string, include []string, @@ -265,7 +269,7 @@ func FindFilterFiles( return ofiles, errors.Wrapf(err, "Failed to change path: %s", path) } - for i := range(ignore) { + for i := range ignore { ignore[i] = strings.TrimLeft(ignore[i], "/") ignore[i] = strings.TrimPrefix(ignore[i], "./") wwlog.Debug("Ignore pattern (%d): %s", i, ignore[i]) @@ -282,7 +286,6 @@ func FindFilterFiles( dev := path_stat.Sys().(*syscall.Stat_t).Dev - includeDirs := []string{} ignoreDirs := []string{} err = filepath.Walk(".", func(location string, info os.FileInfo, err error) error { @@ -306,13 +309,13 @@ func FindFilterFiles( return nil } - for _, ignoreDir := range(ignoreDirs) { + for _, ignoreDir := range ignoreDirs { if strings.HasPrefix(location, ignoreDir) { wwlog.Debug("Ignored (dir): %s", file) return nil } } - for i, pattern := range(ignore) { + for i, pattern := range ignore { m, err := filepath.Match(pattern, location) if err != nil { return err @@ -325,14 +328,14 @@ func FindFilterFiles( } } - for _, includeDir := range(includeDirs) { + for _, includeDir := range includeDirs { if strings.HasPrefix(location, includeDir) { wwlog.Debug("Included (dir): %s", file) ofiles = append(ofiles, location) return nil } } - for i, pattern := range(include) { + for i, pattern := range include { m, err := filepath.Match(pattern, location) if err != nil { return err @@ -352,7 +355,7 @@ func FindFilterFiles( return ofiles, err } -//****************************************************************************** +// ****************************************************************************** func ExecInteractive(command string, a ...string) error { wwlog.Debug("ExecInteractive(%s, %s)", command, a) c := exec.Command(command, a...) @@ -538,20 +541,22 @@ func AppendLines(fileName string, lines []string) error { return nil } -/******************************************************************************* +/* +****************************************************************************** + Create an archive using cpio */ func CpioCreate( ifiles []string, ofile string, format string, - cpio_args ...string ) (err error) { + cpio_args ...string) (err error) { args := []string{ "--quiet", "--create", "-H", format, - "--file=" + ofile } + "--file=" + ofile} args = append(args, cpio_args...) @@ -574,14 +579,16 @@ func CpioCreate( wwlog.Debug(string(out)) } - return FirstError(err, <- err_in) + return FirstError(err, <-err_in) } -/******************************************************************************* +/* +****************************************************************************** + Compress a file using gzip or pigz */ func FileGz( - file string ) (err error) { + file string) (err error) { file_gz := file + ".gz" @@ -608,41 +615,41 @@ func FileGz( proc := exec.Command( compressor, "--keep", - file ) + file) out, err := proc.CombinedOutput() if len(out) > 0 { outStr := string(out[:]) if err != nil && strings.HasSuffix(compressor, "gzip") && strings.Contains(outStr, "unrecognized option") { - var gzippedFile *os.File - var gzipStderr io.ReadCloser - + var gzippedFile *os.File + var gzipStderr io.ReadCloser + /* Older version of gzip, try it another way: */ wwlog.Verbose("%s does not recognize the --keep flag, trying redirected stdout", compressor) - + /* Open the output file for writing: */ gzippedFile, err = os.Create(file_gz) if err != nil { return errors.Wrapf(err, "Unable to open compressed image file for writing: %s", file_gz) } - + /* We'll execute gzip with output to stdout and attach stdout to the compressed file we just created: - */ + */ proc = exec.Command( compressor, "--stdout", - file ) + file) proc.Stdout = gzippedFile gzipStderr, err = proc.StderrPipe() if err != nil { return errors.Wrapf(err, "Unable to open stderr pipe for compression program: %s", compressor) } - + /* Execute the command: */ err = proc.Start() if err != nil { - _ = proc.Wait() + _ = proc.Wait() gzippedFile.Close() os.Remove(file_gz) err = errors.Wrapf(err, "Unable to successfully execute compression program: %s", compressor) @@ -664,7 +671,9 @@ func FileGz( return err } -/******************************************************************************* +/* +****************************************************************************** + Create an archive using cpio */ func BuildFsImage( @@ -675,7 +684,7 @@ func BuildFsImage( ignore []string, ignore_xdev bool, format string, - cpio_args ...string ) (err error) { + cpio_args ...string) (err error) { err = os.MkdirAll(path.Dir(imagePath), 0755) if err != nil { @@ -709,16 +718,16 @@ func BuildFsImage( ".", include, ignore, - ignore_xdev ) + ignore_xdev) if err != nil { return errors.Wrapf(err, "Failed discovering files for %s: %s", name, rootfsPath) } err = CpioCreate( files, - imagePath, + imagePath, format, - cpio_args...) + cpio_args...) if err != nil { return errors.Wrapf(err, "Failed creating image for %s: %s", name, imagePath) } @@ -727,15 +736,17 @@ func BuildFsImage( err = FileGz(imagePath) if err != nil { - return errors.Wrapf(err, "Failed to compress image for %s: %s", name, imagePath + ".gz") + return errors.Wrapf(err, "Failed to compress image for %s: %s", name, imagePath+".gz") } - wwlog.Info("Compressed image for %s: %s", name, imagePath + ".gz") + wwlog.Info("Compressed image for %s: %s", name, imagePath+".gz") return nil } -/******************************************************************************* +/* +****************************************************************************** + Runs wwctl command */ func RunWWCTL(args ...string) (out []byte, err error) { @@ -744,7 +755,7 @@ func RunWWCTL(args ...string) (out []byte, err error) { running, queued := ProcessLimitedStatus() wwlog.Verbose("Starting wwctl process %d (%d running, %d queued): %v", - index, running, queued, args ) + index, running, queued, args) proc := exec.Command("wwctl", args...) @@ -787,3 +798,21 @@ func ByteToString(b int64) string { } return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp]) } + +/* +Check if the w-bit of a file/dir. unix.Access(file,unix.W_OK) will +not show this. +*/ +func IsWriteAble(path string) bool { + info, err := os.Stat(path) + if err != nil { + return false + } + + // Check if the user bit is enabled in file permission + if info.Mode().Perm()&(1<<(uint(7))) == 0 { + wwlog.Debug("Write permission bit is not set for: %s", path) + return false + } + return true +} diff --git a/internal/pkg/warewulfconf/constructors.go b/internal/pkg/warewulfconf/constructors.go index 1d68564a..c434d66e 100644 --- a/internal/pkg/warewulfconf/constructors.go +++ b/internal/pkg/warewulfconf/constructors.go @@ -3,8 +3,8 @@ package warewulfconf import ( "errors" "fmt" - "io/ioutil" "net" + "os" "path" "github.com/brotherpowers/ipsubnet" @@ -36,14 +36,20 @@ func New() (ControllerConf, error) { ret.Tftp = &tftpconf ret.Nfs = &nfsConf err := defaults.Set(&ret) + // ipxe binaries are merged not overwritten, store defaults separate + defIpxe := make(map[string]string) + for k, v := range ret.Tftp.IpxeBinaries { + defIpxe[k] = v + delete(ret.Tftp.IpxeBinaries, k) + } if err != nil { - wwlog.Error("Coult initialize default variables") + wwlog.Error("Could initialize default variables") return ret, err } // Check if cached config is old before re-reading config file if !cachedConf.current { wwlog.Debug("Opening Warewulf configuration file: %s", ConfigFile) - data, err := ioutil.ReadFile(ConfigFile) + data, err := os.ReadFile(ConfigFile) if err != nil { wwlog.Warn("Error reading Warewulf configuration file") } @@ -53,7 +59,9 @@ func New() (ControllerConf, error) { if err != nil { return ret, err } - + if len(ret.Tftp.IpxeBinaries) == 0 { + ret.Tftp.IpxeBinaries = defIpxe + } if ret.Ipaddr == "" || ret.Netmask == "" { conn, error := net.Dial("udp", "8.8.8.8:80") if error != nil { diff --git a/internal/pkg/warewulfconf/datastructure.go b/internal/pkg/warewulfconf/datastructure.go index c3eebf8c..8403e878 100644 --- a/internal/pkg/warewulfconf/datastructure.go +++ b/internal/pkg/warewulfconf/datastructure.go @@ -7,19 +7,20 @@ import ( ) type ControllerConf struct { - WWInternal int `yaml:"WW_INTERNAL"` - Comment string `yaml:"comment,omitempty"` - Ipaddr string `yaml:"ipaddr"` - Ipaddr6 string `yaml:"ipaddr6,omitempty"` - Netmask string `yaml:"netmask"` - Network string `yaml:"network,omitempty"` - Ipv6net string `yaml:"ipv6net,omitempty"` - Fqdn string `yaml:"fqdn,omitempty"` - Warewulf *WarewulfConf `yaml:"warewulf"` - Dhcp *DhcpConf `yaml:"dhcp"` - Tftp *TftpConf `yaml:"tftp"` - Nfs *NfsConf `yaml:"nfs"` - current bool + WWInternal int `yaml:"WW_INTERNAL"` + Comment string `yaml:"comment,omitempty"` + Ipaddr string `yaml:"ipaddr"` + Ipaddr6 string `yaml:"ipaddr6,omitempty"` + Netmask string `yaml:"netmask"` + Network string `yaml:"network,omitempty"` + Ipv6net string `yaml:"ipv6net,omitempty"` + Fqdn string `yaml:"fqdn,omitempty"` + Warewulf *WarewulfConf `yaml:"warewulf"` + Dhcp *DhcpConf `yaml:"dhcp"` + Tftp *TftpConf `yaml:"tftp"` + Nfs *NfsConf `yaml:"nfs"` + MountsContainer []*MountEntry `yaml:"container mounts"` + current bool } type WarewulfConf struct { @@ -44,6 +45,8 @@ type TftpConf struct { Enabled bool `yaml:"enabled" default:"true"` TftpRoot string `yaml:"tftproot" default:"/var/lib/tftpboot"` SystemdName string `yaml:"systemd name" default:"tftp"` + // Path is relative to buildconfig.DATADIR() + IpxeBinaries map[string]string `yaml:"ipxe" default:"{\"00:09\": \"x86_64.efi\",\"00:00\": \"x86_64.kpxe\",\"00:0B\": \"arm64.efi\",\"00:07\": \"x86_64.efi\"}"` } type NfsConf struct { @@ -59,6 +62,16 @@ type NfsExportConf struct { Mount bool `default:"true" yaml:"mount"` } +/* +Describe a mount point for a container exec +*/ +type MountEntry struct { + Source string `yaml:"source" default:"/etc/resolv.conf"` + Dest string `yaml:"dest,omitempty" default:"/etc/resolv.conf"` + ReadOnly bool `yaml:"readonly,omitempty" default:"false"` + Options string `yaml:"options,omitempty"` // ignored at the moment +} + func (s *NfsConf) Unmarshal(unmarshal func(interface{}) error) error { if err := defaults.Set(s); err != nil { return err diff --git a/internal/pkg/warewulfd/daemon.go b/internal/pkg/warewulfd/daemon.go index 4eec8c21..2ed57ed4 100644 --- a/internal/pkg/warewulfd/daemon.go +++ b/internal/pkg/warewulfd/daemon.go @@ -2,7 +2,6 @@ package warewulfd import ( "fmt" - "io/ioutil" "log/syslog" "os" "os/exec" @@ -41,7 +40,7 @@ func DaemonInitLogging() error { if err == nil { wwlog.SetLogLevel(level) } - }else{ + } else { wwlog.SetLogLevel(wwlog.SERV) } @@ -86,7 +85,7 @@ func DaemonStart() error { logLevel := wwlog.GetLogLevel() if logLevel == wwlog.INFO { os.Setenv("WAREWULFD_LOGLEVEL", strconv.Itoa(wwlog.SERV)) - }else{ + } else { os.Setenv("WAREWULFD_LOGLEVEL", strconv.Itoa(logLevel)) } @@ -124,7 +123,7 @@ func DaemonStatus() error { return errors.New("Warewulf server is not running") } - dat, err := ioutil.ReadFile(WAREWULFD_PIDFILE) + dat, err := os.ReadFile(WAREWULFD_PIDFILE) if err != nil { return errors.Wrap(err, "could not read Warewulfd PID file") } @@ -150,7 +149,7 @@ func DaemonReload() error { return errors.New("Warewulf server is not running") } - dat, err := ioutil.ReadFile(WAREWULFD_PIDFILE) + dat, err := os.ReadFile(WAREWULFD_PIDFILE) if err != nil { return errors.Wrap(err, "could not read Warewulfd PID file") } @@ -169,7 +168,7 @@ func DaemonReload() error { logLevel := wwlog.GetLogLevel() if logLevel == wwlog.INFO { os.Setenv("WAREWULFD_LOGLEVEL", strconv.Itoa(wwlog.SERV)) - }else{ + } else { os.Setenv("WAREWULFD_LOGLEVEL", strconv.Itoa(logLevel)) } @@ -182,7 +181,7 @@ func DaemonStop() error { return nil } - dat, err := ioutil.ReadFile(WAREWULFD_PIDFILE) + dat, err := os.ReadFile(WAREWULFD_PIDFILE) if err != nil { return err } diff --git a/overlays/debug/warewulf/template-variables.md.ww b/overlays/debug/warewulf/template-variables.md.ww index 7b47d9e7..8c92c4f5 100644 --- a/overlays/debug/warewulf/template-variables.md.ww +++ b/overlays/debug/warewulf/template-variables.md.ww @@ -165,4 +165,4 @@ data from other structures. - Primary: {{ $netdev.Primary.Get }} - Tags: {{ range $key, $value := $netdev.Tags }}{{ $key }}={{ $value.Get }} {{ end }} {{- end }} -{{- end }} +{{ end }} diff --git a/overlays/generic/etc/hosts.ww b/overlays/generic/etc/hosts.ww index 6ab23cdc..3fe21b65 100644 --- a/overlays/generic/etc/hosts.ww +++ b/overlays/generic/etc/hosts.ww @@ -4,16 +4,14 @@ # Warewulf Server -{{$.Ipaddr}} warewulf {{$.BuildHost}} +{{$.Ipaddr}} {{$.BuildHost}} warewulf {{- range $node := $.AllNodes}} {{/* for each node */}} # Entry for {{$node.Id.Get}} {{- range $devname, $netdev := $node.NetDevs}} {{/* for each network device on the node */}} {{- if $netdev.Ipaddr.Defined}} {{/* if we have an ip address on this network device */}} {{- /* emit the node name as hostname if this is the primary */}} -{{$netdev.Ipaddr.Get}} {{$node.Id.Get}}-{{$devname}} -{{- if $netdev.Device.Defined}} {{$node.Id.Get}}-{{$netdev.Device.Get}}{{end}} -{{- if $netdev.Primary.GetB}} {{$node.Id.Get}}{{end}} -{{- end}} {{/* end if ip */}} -{{- end}} {{/* end for each network device */}} -{{- end}} {{/* end for each node */}} +{{$netdev.Ipaddr.Get}} {{if $netdev.Primary.GetB}}{{$node.Id.Get}}{{end}} {{$node.Id.Get}}-{{$devname}} {{if $netdev.Device.Defined}}{{$node.Id.Get}}-{{$netdev.Device.Get}}{{end}} +{{- end }}{{/* if ip */}} +{{- end }}{{/* for each network device */}} +{{- end }}{{/* for each node */}} diff --git a/overlays/host/etc/dhcp/dhcpd.conf.ww b/overlays/host/etc/dhcp/dhcpd.conf.ww index 82b96cec..66b9d22e 100644 --- a/overlays/host/etc/dhcp/dhcpd.conf.ww +++ b/overlays/host/etc/dhcp/dhcpd.conf.ww @@ -20,15 +20,11 @@ option architecture-type code 93 = unsigned integer 16; if exists user-class and option user-class = "iPXE" { filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}"; } else { - if option architecture-type = 00:0B { - filename "/warewulf/arm64.efi"; - } elsif option architecture-type = 00:09 { - filename "/warewulf/x86_64.efi"; - } elsif option architecture-type = 00:07 { - filename "/warewulf/x86_64.efi"; - } elsif option architecture-type = 00:00 { - filename "/warewulf/x86_64.kpxe"; - } +{{range $type,$name := $.Tftp.IpxeBinaries }} + if option architecture-type = {{ $type }} { + filename "/warewulf/{{ $name }}"; + } +{{ end }} } {{if eq .Dhcp.Template "static" -}} diff --git a/overlays/host/etc/hosts.ww b/overlays/host/etc/hosts.ww index 3f5f9caf..92a93378 100644 --- a/overlays/host/etc/hosts.ww +++ b/overlays/host/etc/hosts.ww @@ -6,16 +6,14 @@ # Warewulf Server -{{$.Ipaddr}} warewulf {{$.BuildHost}} +{{$.Ipaddr}} {{$.BuildHost}} warewulf {{- range $node := $.AllNodes}} {{/* for each node */}} # Entry for {{$node.Id.Get}} {{- range $devname, $netdev := $node.NetDevs}} {{/* for each network device on the node */}} {{- if $netdev.Ipaddr.Defined}} {{/* if we have an ip address on this network device */}} {{- /* emit the node name as hostname if this is the primary */}} -{{$netdev.Ipaddr.Get}} {{$node.Id.Get}}-{{$devname}} -{{- if $netdev.Device.Defined}} {{$node.Id.Get}}-{{$netdev.Device.Get}}{{end}} -{{- if $netdev.Primary.GetB}} {{$node.Id.Get}}{{end}} -{{- end}} {{/* end if ip */}} -{{- end}} {{/* end for each network device */}} -{{- end}} {{/* end for each node */}} +{{$netdev.Ipaddr.Get}} {{if $netdev.Primary.GetB}}{{$node.Id.Get}}{{end}} {{$node.Id.Get}}-{{$devname}} {{if $netdev.Device.Defined}}{{$node.Id.Get}}-{{$netdev.Device.Get}}{{end}} +{{- end }}{{/* if ip */}} +{{- end }}{{/* for each network device */}} +{{- end }}{{/* for each node */}} diff --git a/print_mnts b/print_mnts new file mode 100755 index 00000000..28986c71 Binary files /dev/null and b/print_mnts differ diff --git a/userdocs/conf.py b/userdocs/conf.py index 573a75e7..3420cbe6 100644 --- a/userdocs/conf.py +++ b/userdocs/conf.py @@ -7,14 +7,14 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'Warewulf User Guide' -copyright = '2022, Warewulf Project Contributors' +copyright = '2023, Warewulf Project Contributors' author = 'Warewulf Project Contributors' release = 'development' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = [] +extensions = ['sphinx.ext.autosectionlabel'] templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] @@ -45,4 +45,4 @@ html_context = { html_logo = 'logo.png' html_favicon = 'favicon.png' -html_show_copyright = False \ No newline at end of file +html_show_copyright = False diff --git a/userdocs/contents/background.rst b/userdocs/contents/background.rst index 8547b489..52899dfe 100644 --- a/userdocs/contents/background.rst +++ b/userdocs/contents/background.rst @@ -2,17 +2,37 @@ Background ========== -Warewulf is based on the design of the original Beowulf Cluster design (and thus the name, soft\ **WARE** implementation of the beo\ **WULF**) +Warewulf is based on the design of the original Beowulf Cluster design +(and thus the name, soft\ **WARE** implementation of the beo\ +**WULF**) -The `Beowulf Cluster `_ design was developed in 1996 by Dr. Thomas Sterling and Dr. Donald Becker at NASA. The architecture is defined as a group of similar compute worker nodes all connected together using standard commodity equipment on a private network segment. The control node (historically referred to as the "master" or "head" node) is dual homed (has two network interface cards) with one of these network interface cards attached to the upstream public network and the other connected to a private network which connects to all of the compute worker nodes (as seen in the figure below). +The `Beowulf Cluster `_ +design was developed in 1996 by Dr. Thomas Sterling and Dr. Donald +Becker at NASA. The architecture is defined as a group of similar +compute worker nodes all connected together using standard commodity +equipment on a private network segment. The control node (historically +referred to as the "master" or "head" node) is dual homed (has two +network interface cards) with one of these network interface cards +attached to the upstream public network and the other connected to a +private network which connects to all of the compute worker nodes (as +seen in the figure below). .. image:: beowulf_architecture.png :alt: Beowulf architecture -This simple topology is the foundation for creating every scalable HPC cluster resource. Even today, almost 30 years after the inception of this architecture, this is the baseline architecture that traditional HPC systems are built to. +This simple topology is the foundation for creating every scalable HPC +cluster resource. Even today, almost 30 years after the inception of +this architecture, this is the baseline architecture that traditional +HPC systems are built to. -Other considerations for a working HPC-type cluster are storage, scheduling and resource management, monitoring, interactive systems, etc. For smaller systems, much of these requirements can be managed all from a single control node, but as the system scales, it may need to have groups of nodes dedicated to these different services. +Other considerations for a working HPC-type cluster are storage, +scheduling and resource management, monitoring, interactive systems, +etc. For smaller systems, much of these requirements can be managed +all from a single control node, but as the system scales, it may need +to have groups of nodes dedicated to these different services. -Warewulf is easily capable of building simple and turnkey HPC clusters, to giant massive complex multi-purpose computing clusters, through next generation computing platforms. +Warewulf is easily capable of building simple and turnkey HPC +clusters, to giant massive complex multi-purpose computing clusters, +through next generation computing platforms. -Anytime a cluster of systems is needed, Warewulf is your tool! \ No newline at end of file +Anytime a cluster of systems is needed, Warewulf is your tool! diff --git a/userdocs/contents/configuration.rst b/userdocs/contents/configuration.rst index 2fc2f04d..7eb204a5 100644 --- a/userdocs/contents/configuration.rst +++ b/userdocs/contents/configuration.rst @@ -50,12 +50,13 @@ Generally you can leave this file as is, as long as you set the appropriate networking information. Specifically the following configurations: -* ``ipaddr``: This is the control node's networking interface connecting - to the cluster's **PRIVATE** network. This configuration must match - the host's network IP address for the cluster's private interface. +* ``ipaddr``: This is the control node's networking interface + connecting to the cluster's **PRIVATE** network. This configuration + must match the host's network IP address for the cluster's private + interface. -* ``netmask``: Similar to the ``ipaddr``, this is the subnet mask for the - cluster's **PRIVATE** network and it must also match the host's +* ``netmask``: Similar to the ``ipaddr``, this is the subnet mask for + the cluster's **PRIVATE** network and it must also match the host's subnet mask for the cluster's private interface. * ``dhcp:range start`` and ``dhcp:range end``: This address range must @@ -64,9 +65,9 @@ configurations: you want DHCP to use. .. note:: - The network configuration listed above assumes the network - layout in the [Background](background.md) portion of the - documentation. + + The network configuration listed above assumes the network layout + in the [Background](background.md) portion of the documentation. The other configuration options are usually not touched, but they are explained as follows: @@ -74,34 +75,35 @@ explained as follows: * ``*:enabled``: This disables Warewulf's control of an external service. This is useful if you want to manage that service directly. -* ``*:systemd name``: This is so Warewulf can control some of the host's - services. For the distributions that we've built and tested this on, - these will require no changes. +* ``*:systemd name``: This is so Warewulf can control some of the + host's services. For the distributions that we've built and tested + this on, these will require no changes. -* ``warewulf:port``: This is the port that the Warewulf web server will - be listening on. It is recommended not to change this so there is no - misalignment with node's expectations of how to contact the Warewulf - service. +* ``warewulf:port``: This is the port that the Warewulf web server + will be listening on. It is recommended not to change this so there + is no misalignment with node's expectations of how to contact the + Warewulf service. -* ``warewulf:secure``: When ``true``, this limits the Warewulf server to - only respond to runtime overlay requests originating from a +* ``warewulf:secure``: When ``true``, this limits the Warewulf server + to only respond to runtime overlay requests originating from a privileged report port. This makes it so that only the ``root`` user on a compute node can request the runtime overlay. While generally there is nothing super "secure" in these overlays, this adds the necessary protection that the user's can not obtain this information. -* ``warewulf:update interval``: This defines the frequency (in seconds) - with which the Warewulf client on the compute node fetches overlay - updates. +* ``warewulf:update interval``: This defines the frequency (in + seconds) with which the Warewulf client on the compute node fetches + overlay updates. * ``warewulf:autobuild overlays``: This determines whether per-node overlays will automatically be rebuilt, e.g., when an underlying overlay is changed. -* ``warewulf:host overlay``: This determines whether the special ``host`` - overlay is applied to the Warewulf server during configuration. (The - host overlay is used to configure the dependent services.) +* ``warewulf:host overlay``: This determines whether the special + ``host`` overlay is applied to the Warewulf server during + configuration. (The host overlay is used to configure the dependent + services.) * ``warewulf:syslog``: This determines whether Warewulf server logs go to syslog or are written directly to a log file. (e.g., @@ -113,7 +115,7 @@ explained as follows: nodes.conf ========== -The ``nodes.conf`` is the primary database file for all compute +The ``nodes.conf`` file is the primary database file for all compute nodes. It is a flat text YAML configuration file that is managed by the ``wwctl`` command, but some sites manage the compute nodes and infrastructure via configuration management. This file being flat text @@ -125,12 +127,50 @@ format of this file as it is recommended to edit with the ``wwctl`` command. .. note:: + This configuration is not written at install time, but the first time you attempt to run ``wwctl``, this file will be generated if it does not exist already. +defaults.conf +============= + +The ``defaults.conf`` file configures default values used when none +are specified. For example: if a node does not have a "runtime +overlay" specified, the respective value from ``defaultnode`` is +used. If a network device does not specify a "device," the device +value of the ``dummy`` device is used. + +If ``defaults.conf`` does not exist, the following values are used as +compiled into Warewulf at build-time: + +.. code-block:: yaml + + -- + defaultnode: + runtime overlay: + - generic + system overlay: + - wwinit + kernel: + args: quiet crashkernel=no vga=791 net.naming-scheme=v238 + init: /sbin/init + root: initramfs + ipxe template: default + profiles: + - default + network devices: + dummy: + device: eth0 + type: ethernet + netmask: 255.255.255.0 + +There should never be a need to change this file: all site-local +parameters should be specified using either nodes or profiles. + Directories =========== -The ``/etc/warewulf/ipxe/`` contains *text/templates* that are used by -the Warewulf configuration process to configure the ``ipxe`` service. \ No newline at end of file +The ``/etc/warewulf/ipxe/`` directory contains *text/templates* that +are used by the Warewulf configuration process to configure the +``ipxe`` service. diff --git a/userdocs/contents/containers.rst b/userdocs/contents/containers.rst index 4b186545..c8b0a5d3 100644 --- a/userdocs/contents/containers.rst +++ b/userdocs/contents/containers.rst @@ -2,29 +2,53 @@ Container Management ==================== -Since the inception of Warewulf over 20 years ago, Warewulf has used the model of the "Virtual Node File System" (VNFS) as a template image for the compute nodes. This is similar to a golden master image, except that the node file system exists within a directory on the Warewulf control node (e.g. a `chroot()`). +Since the inception of Warewulf over 20 years ago, Warewulf has used +the model of the "Virtual Node File System" (VNFS) as a template image +for the compute nodes. This is similar to a golden master image, +except that the node file system exists within a directory on the +Warewulf control node (e.g. a ``chroot()``). -In hindsight, we've been using containers all along, but the buzzword just didn't exist. Over the last 5-6 years, the enterprise has created a lot of tooling and standards around defining, building, distributing, securing, and managing containers, so Warewulf (as of v4.0) now integrates directly within the container ecosystem to facilitate the process of VNFS image management. +In hindsight, we've been using containers all along, but the buzzword +just didn't exist. Over the last 5-6 years, the enterprise has created +a lot of tooling and standards around defining, building, +distributing, securing, and managing containers, so Warewulf (as of +v4.0) now integrates directly within the container ecosystem to +facilitate the process of VNFS image management. -If you are not currently leveraging the container ecosystem in any other way, you can still build your own chroot directories and use Warewulf as you always have. +If you are not currently leveraging the container ecosystem in any +other way, you can still build your own chroot directories and use +Warewulf as you always have. -It is important to understand that Warewulf is not running a container runtime on the nodes. While that is absolutely possible to run containers from the booted hosts, Warewulf is provisioning the container image to the bare metal and booting it. This container will be used as the base operating system and by default it will run stateless in memory. This means when you reboot the node, the node persists no information about Warewulf or how it booted. +It is important to understand that Warewulf is not running a container +runtime on the nodes. While that is absolutely possible to run +containers from the booted hosts, Warewulf is provisioning the +container image to the bare metal and booting it. This container will +be used as the base operating system and by default it will run +stateless in memory. This means when you reboot the node, the node +persists no information about Warewulf or how it booted. Container Tools =============== -There are different container managment tools available. Docker is probably the most recognizable one in the enterprise. Podman is another one that is gaining traction on the RHEL platforms. In HPC, Singularity is the most utilized container management tool. You can use any of these to create and manage the containers to be later imported into Warewulf. +There are different container managment tools available. Docker is +probably the most recognizable one in the enterprise. Podman is +another one that is gaining traction on the RHEL platforms. In HPC, +Apptainer is the most utilized container management tool. You can use +any of these to create and manage the containers to be later imported +into Warewulf. Importing From A Registry ========================= -Warewulf supports importing an image from any OCI compliant registry. This means you can import from a public registry or from a private registry. +Warewulf supports importing an image from any OCI compliant +registry. This means you can import from a public registry or from a +private registry. Here is an example of importing from Docker Hub. -.. code-block:: bash +.. code-block:: console - $ sudo wwctl container import docker://warewulf/rocky rocky-8 + # wwctl container import docker://warewulf/rocky rocky-8 Getting image source signatures Copying blob d7f16ed6f451 done Copying config da2ca70704 done @@ -35,21 +59,33 @@ Here is an example of importing from Docker Hub. Building container: rocky-8 .. note:: - Most containers in Docker Hub are not "bootable", in that, they have a limited version of Systemd to make them lighter weight for container purposes. For this reason, don't expect any base Docker container (e.g. ``docker://rockylinux`` or ``docker://debian``) to boot properly. They will not, as they will get stuck into a single user mode. The containers in `https://hub.docker.com/u/warewulf `_ are not limited and thus they boot as you would expect. + + Most containers in Docker Hub are not "bootable", in that, they + have a limited version of Systemd to make them lighter weight for + container purposes. For this reason, don't expect any base Docker + container (e.g. ``docker://rockylinux`` or ``docker://debian``) to + boot properly. They will not, as they will get stuck into a single + user mode. The containers in `https://hub.docker.com/u/warewulf + `_ are not limited and thus + they boot as you would expect. Private Registry ---------------- -It is possible to use a private registry that is password protected or does not have the requirement for TLS. In order to do so, you have two choices for handling the credentials. +It is possible to use a private registry that is password protected or +does not have the requirement for TLS. In order to do so, you have two +choices for handling the credentials. * Set environmental variables -* Use ``docker login`` or ``podman login`` which will store the credentials locally +* Use ``docker login`` or ``podman login`` which will store the + credentials locally -Please note, there is no requirement to install and use docker or podman on your control node just for importing images into Warewulf. +Please note, there is no requirement to install and use docker or +podman on your control node just for importing images into Warewulf. Here are the environmental variables that can be used. -.. code-block:: bash +.. code-block:: console WAREWULF_OCI_USERNAME WAREWULF_OCI_PASSWORD @@ -57,68 +93,115 @@ Here are the environmental variables that can be used. Here is an example: -.. code-block:: bash +.. code-block:: console - export WAREWULF_OCI_USERNAME=privateuser - export WAREWULF_OCI_PASSWORD=super-secret-password-or-token - sudo -E wwctl import docker://ghcr.io/privatereg/rocky:8 + # export WAREWULF_OCI_USERNAME=privateuser + # export WAREWULF_OCI_PASSWORD=super-secret-password-or-token + # wwctl import docker://ghcr.io/privatereg/rocky:8 -The above is just an example. Consideration should be done before doing it this way if you are in a security sensitive environment or shared environments. You would not want these showing up in bash history or logs. +The above is just an example. Consideration should be done before +doing it this way if you are in a security sensitive environment or +shared environments. You would not want these showing up in bash +history or logs. + +Syncuser +-------- + +At import time Warewulf checks if the names of the users on the host +match the users and UIDs/GIDs in the imported container. If there is +mismatch, the import command will print out a warning. By setting the +``--syncuser`` flag you advise Warewulf to try to syncronize the users +from the host to the container, which means that ``/etc/passwd`` and +``/etc/group`` of the imported container are updated and all the files +belonning to these UIDs and GIDs will also be updated. + +A check if the users of the host and container matches can be +triggered with the ``syncuser`` command. + +.. code-block:: console + + # wwctl container syncuser container-name + +With the ``--write`` flag it will update the container to match the +user database of the host as described above. + +.. code-block:: console + + wwctl container syncuser --write container-name Listing All Imported Containers =============================== -Once the container has been imported, you can list them all with the following command: +Once the container has been imported, you can list them all with the +following command: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl container list + # wwctl container list CONTAINER NAME BUILT NODES rocky-8 true 0 -Once a container has been imported and showing up in this list you can configure it to boot compute nodes. +Once a container has been imported and showing up in this list you can +configure it to boot compute nodes. Making Changes To Containers ============================ -Warewulf has a minimal container runtime built into it. This means you can run commands inside of any of the containers and make changes to them as follows: +Warewulf has a minimal container runtime built into it. This means you +can run commands inside of any of the containers and make changes to +them as follows: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl container exec rocky-8 /bin/sh + # wwctl container exec rocky-8 /bin/sh [rocky-8] Warewulf> cat /etc/rocky-release Rocky Linux release 8.4 (Green Obsidian) [rocky-8] Warewulf> exit Rebuilding container... [INFO] Skipping (VNFS is current) -You can also ``--bind`` directories from your host into the container when using the exec command. This works as follows: +You can also ``--bind`` directories from your host into the container +when using the exec command. This works as follows: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl container exec --bind /tmp:/mnt rocky-8 /bin/sh + # wwctl container exec --bind /tmp:/mnt rocky-8 /bin/sh [rocky-8] Warewulf> .. note:: - As with any mount command, both the source and the target must exist. This is why the example uses the ``/mnt/`` directory location, as it is almost always present and empty in every Linux distribution (as prescribed by the LSB file hierarchy standard). -When the command completes, if anything within the container changed, the container will be rebuilt into a bootable static object automatically. + As with any mount command, both the source and the target must + exist. This is why the example uses the ``/mnt/`` directory + location, as it is almost always present and empty in every Linux + distribution (as prescribed by the LSB file hierarchy standard). + +When the command completes, if anything within the container changed, +the container will be rebuilt into a bootable static object +automatically. + +If the files ``/etc/passwd`` or ``/etc/group`` were updated, there +will be an additional check to confirm if the users are in sync as +described in `Syncuser`_ section. Creating Containers From Scratch ================================ -You can also create containers from scratch and import those containers into Warewulf as previous versions of Warewulf did. +You can also create containers from scratch and import those +containers into Warewulf as previous versions of Warewulf did. Building A Container From Your Host ----------------------------------- -RPM based distributions, as well as Debian variants can all bootstrap mini ``chroot()`` directories which can then be used to bootstrap your node's container. +RPM based distributions, as well as Debian variants can all bootstrap +mini ``chroot()`` directories which can then be used to bootstrap your +node's container. -For example, on an RPM based Linux distribution with YUM or DNF, you can do something like the following: +For example, on an RPM based Linux distribution with YUM or DNF, you +can do something like the following: -.. code-block:: bash +.. code-block:: console - $ sudo yum install --installroot /tmp/newroot basesystem bash \ + # yum install --installroot /tmp/newroot basesystem bash \ chkconfig coreutils e2fsprogs ethtool filesystem findutils \ gawk grep initscripts iproute iputils net-tools nfs-utils pam \ psmisc rsync sed setup shadow-utils rsyslog tzdata util-linux \ @@ -129,27 +212,50 @@ For example, on an RPM based Linux distribution with YUM or DNF, you can do some You can do something similar with Debian-based distributions: -.. code-block:: bash +.. code-block:: console - sudo apt-get install debootstrap - sudo debootstrap stable /tmp/newroot http://ftp.us.debian.org/debian + # apt-get install debootstrap + # debootstrap stable /tmp/newroot http://ftp.us.debian.org/debian -Once you have created and modified your new ``chroot()``, you can import it into Warewulf with the following command: +Once you have created and modified your new ``chroot()``, you can +import it into Warewulf with the following command: -.. code-block:: bash +.. code-block:: console - sudo wwctl container import /tmp/newroot containername + # wwctl container import /tmp/newroot containername -Building A Container Using Singularity --------------------------------------- +Building A Container Using Apptainer +------------------------------------ -Singularity, a container platform for HPC and performance intensive applications, can also be used to create node containers for Warewulf. There are several Singularity container recipes in the ``containers/Singularity/`` directory and can be found on GitHub at `https://github.com/hpcng/warewulf/tree/main/containers/Singularity `_. +Apptainer, a container platform for HPC and performance intensive +applications, can also be used to create node containers for +Warewulf. There are several Apptainer container recipes in the +``containers/Apptainer/`` directory and can be found on GitHub at +`https://github.com/hpcng/warewulf/tree/main/containers/Apptainer +`_. -You can use these as starting points and adding any additional steps you want in the ``%post`` section of the recipe file. Once you've done that, installing Singularity, building a container sandbox and importing into Warewulf can be done with the following steps: +You can use these as starting points and adding any additional steps +you want in the ``%post`` section of the recipe file. Once you've done +that, installing Apptainer, building a container sandbox and importing +into Warewulf can be done with the following steps: -.. code-block:: bash +.. code-block:: console - sudo yum install epel-release - sudo yum install singularity - sudo singularity build --sandbox /tmp/newroot /path/to/Singularity/recipe.def - sudo wwctl container import /tmp/newroot containername \ No newline at end of file + # yum install epel-release + # yum install Apptainer + # Apptainer build --sandbox /tmp/newroot /path/to/Apptainer/recipe.def + # wwctl container import /tmp/newroot containername + +Building A Container Using Podman +--------------------------------- + +You can also build a container using podman via a ``Dockerfile``. For +this step the container must be exported to a tar archive, which then +can be imported to Warewulf. The following steps will create an +openSUSE Leap container and import it to Warewulf: + +.. code-block:: console + + # podman build -f containers/Docker/openSUSE/Containerfile --tag leap-ww + # podman save localhost/leap-ww:latest -o ~/leap-ww.tar + # wwctl container import file://root/leap-ww.tar leap-ww diff --git a/userdocs/contents/initialization.rst b/userdocs/contents/initialization.rst index 6a9cc197..4d54d13e 100644 --- a/userdocs/contents/initialization.rst +++ b/userdocs/contents/initialization.rst @@ -5,41 +5,65 @@ Warewulf Initialization System Services =============== -Once Warewulf has been installed and configured, it is ready to be initialized and have the associated system services started. To do this, start by configuring the system services that Warewulf depends on for operation. To do that, run the following command: +Once Warewulf has been installed and configured, it is ready to be +initialized and have the associated system services started. To do +this, start by configuring the system services that Warewulf depends +on for operation. To do that, run the following command: -.. code-block:: bash +.. code-block:: console - sudo wwctl configure --all + # wwctl configure --all -This command will configure the system for Warewulf to run properly. Here are the things it will do: +This command will configure the system for Warewulf to run +properly. Here are the things it will do: -* **dhcp**: (re)Write the DHCP configuration and restart the service from the configured template in ``/etc/warewulf/dhcp/`` and enable the system service. -* **hostfile**: Update the system's /etc/hosts file based on the template ``/etc/warewulf/hosts.tmpl``. -* **nfs**: Configure the NFS server on the control node as well as the ``/etc/fstab`` in the system overlay based on the configuration in ``/etc/warewulf/warewulf.conf`` and enable the system service. -* **ssh**: Create the appropriate host keys (stored in ``/etc/warewulf/keys/``) and user keys for passwordless ``ssh`` into the nodes. -* **tftp**: Write the appropriate binary PXE/iPXE blobs to the TFTP root directory and enable the system service. +* **dhcp**: (re)Write the DHCP configuration and restart the service + from the **host** template under ``/etc/dhcpd.conf.ww`` and enable + the system service. +* **hostfile**: Update the system's /etc/hosts file based on the + **host** template ``/etc/hosts.ww``. +* **nfs**: Configure the NFS server on the control node as well as the + ``/etc/fstab`` in the system overlay based on the configuration in + ``/etc/warewulf/warewulf.conf`` and enable the system service. Also + the file ``/etc/exports.ww`` from the **host** template is installed. +* **ssh**: Create the appropriate host keys (stored in + ``/etc/warewulf/keys/``) and user keys for passwordless ``ssh`` into + the nodes. Addionally the shell profiles + ``/etc/profile.d/ssh_setup.csh`` and ``/etc/profile.d/ssh_setup.sh`` + are installed. +* **tftp**: Write the appropriate binary PXE/iPXE blobs to the TFTP + root directory and enable the system service. -This command will quickly setup the system services per the Warewulf configuration. Watch this output carefully for errors and resolve them in the configuration portion of this manual. +This command will quickly setup the system services per the Warewulf +configuration. Watch this output carefully for errors and resolve them +in the configuration portion of this manual. Warewulf Service ================ -The Warewulf installation attempts to register the Warewulf service with Systemd, so it should be as easy to start/stop/check as any other Systemd service: +The Warewulf installation attempts to register the Warewulf service +with Systemd, so it should be as easy to start/stop/check as any other +Systemd service: -.. code-block:: bash +.. code-block:: console - sudo systemctl enable --now warewulfd + # systemctl enable --now warewulfd -You can also check and control the Warewulf service using the `wwctl` command line program as follows: +You can also check and control the Warewulf service using the ``wwctl`` +command line program as follows: -.. code-block:: bash +.. code-block:: console - sudo wwctl server status + # wwctl server status .. note:: - If the Warewulf service is running via Systemd, restarting stopping, and starting it from the ``wwctl`` command may result in unexpected results. + + If the Warewulf service is running via Systemd, restarting + stopping, and starting it from the ``wwctl`` command may result in + unexpected results. Logs ---- -The Warewulf server logs are by default written to ``/var/log/warewulfd.log``. \ No newline at end of file +The Warewulf server logs are by default written to +``/var/log/warewulfd.log``. diff --git a/userdocs/contents/installation.rst b/userdocs/contents/installation.rst index 985193ba..e7b62431 100644 --- a/userdocs/contents/installation.rst +++ b/userdocs/contents/installation.rst @@ -2,41 +2,61 @@ Warewulf Installation ##################### -There are multiple methods to install Warewulf, this page describes the installation process of multiple methods: +There are multiple methods to install Warewulf, this page describes +the installation process of multiple methods: Binary RPMs =========== -While the Warewulf project does not build binary RPMs, you can obtain them from `CIQ `_ and use them for non-production use from their public YUM and DNF repositories at: `https://repo.ctrliq.com `_ +The Warewulf project builds binary RPMs as part of its CI/CD +process. You can obtain them from the `GitHub releases +`_ page. -This is the easiest method to install Warewulf and can be done as follows: +Rocky Linux 8 +------------- -.. code-block:: bash +.. code-block:: console - sudo yum install -y https://repo.ctrliq.com/rhel/8/ciq-release.rpm - sudo yum install -y warewulf + # dnf install https://github.com/hpcng/warewulf/releases/download/v4.4.0/warewulf-4.4.0-1.git_afcdb21.el8.x86_64.rpm -> note: as mentioned, these binaries are part of CIQ's commercial support offering but they can be used for non-production and testing uses. If you are interested in using these binaries for production, please contact CIQ at: `info@ctrliq.com `_. +openSuse Leap +------------- + +.. code-block:: console + + # zypper install https://github.com/hpcng/warewulf/releases/download/v4.4.0/warewulf-4.4.0-1.git_afcdb21.suse.lp153.x86_64.rpm Compiled Source code ==================== -Before you build the Warewulf source code you will first need to install the build dependencies: +Before you build the Warewulf source code you will first need to +install the build dependencies: -* ``make``: This should be available via your Linux distribution's package manager (e.g. ``dnf install make``) -* ``go``: Golang is also available on most current Linux distributions, but if you wish to install the most recent version, you can find that here: `https://golang.org/dl/ `_ -* Depending on your Linux Distribution, you may need to install other development packages. Typically it is recommended to install the entire development group like ``dnf groupinstall "Development Tools"`` +* ``make``: This should be available via your Linux distribution's + package manager (e.g. ``dnf install make``) +* ``go``: Golang is also available on most current Linux + distributions, but if you wish to install the most recent version, + you can find that here: `https://golang.org/dl/ + `_ +* Depending on your Linux Distribution, you may need to install other + development packages. Typically it is recommended to install the + entire development group like ``dnf groupinstall "Development + Tools"`` -Once these dependencies are installed, you can obtain and build the source code as follows: +Once these dependencies are installed, you can obtain and build the +source code as follows: Release Tarball --------------- -When the Warewulf project releases stable versions, they are available via source form here: +When the Warewulf project releases stable versions, they are available +via source form here: `https://github.com/hpcng/warewulf/tags `_ -Select the version you wish to install and download the tarball to any location on the server, then follow these directions making the appropriate substitutions: +Select the version you wish to install and download the tarball to any +location on the server, then follow these directions making the +appropriate substitutions: .. code-block:: bash @@ -54,7 +74,17 @@ Select the version you wish to install and download the tarball to any location Git --- -Warewulf is developed in "Git", a source code management platform that allows collaborative development and revision control. From the Git repository, you can download different versions of the project either from tags or branches. By default, when you go to the GitHub page, you will find the default branch entitled ``main``. The ``main`` branch is where most of the active development occurs, so if you want to obtain the latest and greatest version of Warewulf, this is where to go. But be forewarned, using a snapshot from ``main`` is not guaranteed to be stable or generally supported for production. If you are building for production, it is best to download a release tarball from the main site, the GitHub releases page, or from a Git tag. +Warewulf is developed in "Git", a source code management platform that +allows collaborative development and revision control. From the Git +repository, you can download different versions of the project either +from tags or branches. By default, when you go to the GitHub page, you +will find the default branch entitled ``main``. The ``main`` branch is +where most of the active development occurs, so if you want to obtain +the latest and greatest version of Warewulf, this is where to go. But +be forewarned, using a snapshot from ``main`` is not guaranteed to be +stable or generally supported for production. If you are building for +production, it is best to download a release tarball from the main +site, the GitHub releases page, or from a Git tag. .. code-block:: bash @@ -68,7 +98,9 @@ Warewulf is developed in "Git", a source code management platform that allows co Runtime Dependencies -------------------- -In Warewulf's default configuration, it will require some operating system provided services. Generally these are provided by your installation vendor and can be installed over the network. +In Warewulf's default configuration, it will require some operating +system provided services. Generally these are provided by your +installation vendor and can be installed over the network. These are the services you will need to install: @@ -76,4 +108,5 @@ These are the services you will need to install: * ``tftp-server`` * ``nfs-utils`` -If you are using an Enterprise Linux compatible distribution you can install them with: ``yum install dhcp-server tftp-server nfs-utils`` \ No newline at end of file +If you are using an Enterprise Linux compatible distribution you can +install them with ``yum install dhcp-server tftp-server nfs-utils``. diff --git a/userdocs/contents/introduction.rst b/userdocs/contents/introduction.rst index a1975527..fc09b46f 100644 --- a/userdocs/contents/introduction.rst +++ b/userdocs/contents/introduction.rst @@ -5,32 +5,71 @@ Introduction The Warewulf Vision =================== -Warewulf has had a number of iterations over the last 20 years, but its design tenets have always remained the same: a simple, scalable, stateless (however some versions were able to provision stateful), and very flexible operating system provisioning system for all types of clusters. This is an overview of how Warewulf works. +Warewulf has had a number of iterations over the last 20 years, but +its design tenets have always remained the same: a simple, scalable, +stateless (however some versions were able to provision stateful), and +very flexible operating system provisioning system for all types of +clusters. This is an overview of how Warewulf works. About Warewulf ============== -Warewulf is an operating system provisioning platform for Linux that is designed to produce secure, scalable, turnkey cluster deployments that maintain flexibility and simplicity. +Warewulf is an operating system provisioning platform for Linux that +is designed to produce secure, scalable, turnkey cluster deployments +that maintain flexibility and simplicity. -Since its initial release in 2001, Warewulf has become the most popular open source and vendor-agnostic provisioning system within the global HPC community. Warewulf is known for its massive scalability and simple management of stateless (disk optional) provisioning. +Since its initial release in 2001, Warewulf has become the most +popular open source and vendor-agnostic provisioning system within the +global HPC community. Warewulf is known for its massive scalability +and simple management of stateless (disk optional) provisioning. -Warewulf leverages a simple administrative model centralizing administration around virtual node images which are used to provision out to the cluster nodes. This means you can have hundreds or thousands of cluster nodes all booting and running on the same, identical virtual node file system image. As of Warewulf v4, the virtual node image is a standard container image which means all compute resources within a cluster can be managed using any existing container tooling and/or CI pipelines that are being used. This can be as simple as DockerHub or your own private GitLab CI infrastructure. +Warewulf leverages a simple administrative model centralizing +administration around virtual node images which are used to provision +out to the cluster nodes. This means you can have hundreds or +thousands of cluster nodes all booting and running on the same, +identical virtual node file system image. As of Warewulf v4, the +virtual node image is a standard container image which means all +compute resources within a cluster can be managed using any existing +container tooling and/or CI pipelines that are being used. This can be +as simple as DockerHub or your own private GitLab CI infrastructure. -With this architecture, Warewulf combines the best of High Performance Computing (HPC), Cloud, Hyperscale, and Enterprise deployment principals to create and maintain large scalable stateless clusters. +With this architecture, Warewulf combines the best of High Performance +Computing (HPC), Cloud, Hyperscale, and Enterprise deployment +principals to create and maintain large scalable stateless clusters. -While Warewulf's roots are in HPC, it has been used for many different types of tasks and use cases. Everything from clustered web servers, to rendering farms, and even Kubernetes and cloud deployments, Warewulf brings benefit in experience of general operating system management at scale. +While Warewulf's roots are in HPC, it has been used for many different +types of tasks and use cases. Everything from clustered web servers, +to rendering farms, and even Kubernetes and cloud deployments, +Warewulf brings benefit in experience of general operating system +management at scale. Features ======== -* **Lightweight**: Warewulf provisions stateless operating system images and then gets out of the way. There should be no underlying system dependencies or changes to the provisioned cluster node operating systems. +* **Lightweight**: Warewulf provisions stateless operating system + images and then gets out of the way. There should be no underlying + system dependencies or changes to the provisioned cluster node + operating systems. -* **Simple**: Warewulf is used by hobbyists, researchers, scientists, engineers and systems administrators because it is easy, lightweight, and simple. +* **Simple**: Warewulf is used by hobbyists, researchers, scientists, + engineers and systems administrators because it is easy, + lightweight, and simple. -* **Flexible**: Warewulf is highly flexible and can address the needs of any environment-- from a computer lab with graphical workstations, to under-the-desk clusters, to massive supercomputing centers providing traditional HPC capabilities to thousands of users. +* **Flexible**: Warewulf is highly flexible and can address the needs + of any environment-- from a computer lab with graphical + workstations, to under-the-desk clusters, to massive supercomputing + centers providing traditional HPC capabilities to thousands of + users. -* **Agnostic**: From the Linux distribution of choice to the underlying hardware, Warewulf is agnostic and standards compliant. From ARM to x86, Atos to Dell, Debian, SuSE, Rocky, CentOS, and RHEL, Warewulf can do it all. +* **Agnostic**: From the Linux distribution of choice to the + underlying hardware, Warewulf is agnostic and standards + compliant. From ARM to x86, Atos to Dell, Debian, SuSE, Rocky, + CentOS, and RHEL, Warewulf can do it all. -* **Secure**: Warewulf is the only stateless provisioning system that will support SELinux out of the box. Just enable your node operating system container to support SELinux, and Warewulf do the rest! +* **Secure**: Warewulf is the only stateless provisioning system that + will support SELinux out of the box. Just enable your node operating + system container to support SELinux, and Warewulf do the rest! -* **Open Source**: For the last 20 years, Warewulf has remained open source and continues to be the golden standard for cluster provisioning. \ No newline at end of file +* **Open Source**: For the last 20 years, Warewulf has remained open + source and continues to be the golden standard for cluster + provisioning. diff --git a/userdocs/contents/ipmi.rst b/userdocs/contents/ipmi.rst index fe1a7a94..8c8fdec5 100644 --- a/userdocs/contents/ipmi.rst +++ b/userdocs/contents/ipmi.rst @@ -2,120 +2,152 @@ IPMI ==== -It is possible to control the power or connect a console to your nodes being managed by Warewulf by connecting to the BMC through the use of IPMI. We will discuss how to set this up below. +It is possible to control the power or connect a console to your nodes +being managed by Warewulf by connecting to the BMC through the use of +IPMI. We will discuss how to set this up below. IPMI Settings ============= -The common settings for the IPMI interfaces on all nodes can be set on a Profile level. The only field that would need to be assigned to each individual node would be the IP address. +The common settings for the IPMI interfaces on all nodes can be set on +a Profile level. The only field that would need to be assigned to each +individual node would be the IP address. -If an individual node has different settings, you can set the IPMI settings for that specific node, overriding the default settings. +The settings are only written to the IPMI interface if ``--ipmiwrite`` +is set to `true`. The write process happens at every boot of the node +through the script ``/warewulf/init.d/50-ipmi`` in the **system** +overlay. -Here is a table outlining the fields on a Profile and Node level, along with the parameters that can be used when running ``wwctl profile set`` or ``wwctl node set``. +If an individual node has different settings, you can set the IPMI +settings for that specific node, overriding the default settings. + +Here is a table outlining the fields on a Profile and Node which is +the same as the parameter that can be used when running ``wwctl +profile set`` or ``wwctl node set``. + ++--------------------+---------+------+--------------------+---------------+ +| Parameter | Profile | Node | Valid Values | Default Value | ++====================+=========+======+====================+===============+ +| ``--ipmiaddr`` | false | true | | | ++--------------------+---------+------+--------------------+---------------+ +| ``--ipminetmask`` | true | true | | | ++--------------------+---------+------+--------------------+---------------+ +| ``--ipmiport`` | true | true | | 623 | ++--------------------+---------+------+--------------------+---------------+ +| ``--ipmigateway`` | true | true | | | ++--------------------+---------+------+--------------------+---------------+ +| ``--ipmiuser`` | true | true | | | ++--------------------+---------+------+--------------------+---------------+ +| ``--ipmipass`` | true | true | | | ++--------------------+---------+------+--------------------+---------------+ +| ``--ipmiinterface``| true | true | 'lan' or 'lanplus' | lan | ++--------------------+---------+------+--------------------+---------------+ +| ``--ipmiwrite`` | true | true | true or false | false | ++--------------------+---------+------+--------------------+---------------+ -============= =============== ======== ======= ================== ============= -Field Parameter Profile Node Valid Values Default Value -============= =============== ======== ======= ================== ============= -IpmiIpaddr --ipmi X -IpmiNetmask --ipminetmask X X -IpmiPort --ipmiport X X 623 -IpmiGateway --ipmigateway X X -IpmiUserName --ipmiuser X X -IpmiPassword --ipmipass X X -IpmiInterface --ipmiinterface X X 'lan' or 'lanplus' lan -============= =============== ======== ======= ================== ============= Reviewing Settings ================== -There are multiple ways to review the IPMI settings. They can be reviewed from a Profile level, all the way down to a specific Node. +There are multiple ways to review the IPMI settings. They can be +reviewed from a Profile level, all the way down to a specific Node. Profile View ------------ -.. code-block:: bash +.. code-block:: console - $ sudo wwctl profile list -a + # wwctl profile list -a + PROFILE FIELD PROFILE VALUE + ===================================================================================== + default Id -- default + default comment -- This profile is automatically included for each node + default cluster -- -- + default container -- sle-micro-5.3 + default ipxe -- -- + default runtime -- -- + default wwinit -- -- + default root -- -- + default discoverable -- -- + default init -- -- + default asset -- -- + default profile -- -- + default default:type -- -- + default default:onboot -- -- + default default:netdev -- -- + default default:hwaddr -- -- + default default:ipaddr -- -- + default default:ipaddr6 -- -- + default default:netmask -- -- + default default:gateway -- -- + default default:mtu -- -- + default default:primary -- -- - ################################################################################ - PROFILE NAME FIELD VALUE - default Id default - default Comment This profile is automatically included for each node - default Cluster -- - default Container rocky - default Kernel 4.18.0-348.2.1.el8_5.x86_64 - default KernelArgs -- - default Init -- - default Root -- - default RuntimeOverlay -- - default SystemOverlay -- - default Ipxe -- - default IpmiNetmask 255.255.255.0 - default IpmiPort -- - default IpmiGateway 192.168.99.1 - default IpmiUserName admin - default IpmiInterface lanplus - default eth0:IPADDR -- - default eth0:NETMASK 255.255.240.0 - default eth0:GATEWAY 10.1.96.6 - default eth0:HWADDR -- - default eth0:TYPE -- - default eth0:DEFAULT false Node View --------- -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node list node0001 -a - - ################################################################################ - NODE FIELD PROFILE VALUE - node0001 Id -- node0001 - node0001 Comment default This profile is automatically included for each node - node0001 Cluster -- -- - node0001 Profiles -- default - node0001 Discoverable -- false - node0001 Container default rocky - node0001 Kernel default 4.18.0-348.2.1.el8_5.x86_64 - node0001 KernelArgs -- (quiet crashkernel=no vga=791 rootfstype=rootfs) - node0001 RuntimeOverlay -- (default) - node0001 SystemOverlay -- (default) - node0001 Ipxe -- (default) - node0001 Init -- (/sbin/init) - node0001 Root -- (initramfs) - node0001 IpmiIpaddr -- 192.168.99.10 - node0001 IpmiNetmask -- 255.255.255.0 - node0001 IpmiPort -- -- - node0001 IpmiGateway -- 192.168.99.1 - node0001 IpmiUserName default admin - node0001 IpmiInterface default lanplus - node0001 eth0:HWADDR -- 52:54:00:1a:08:60 - node0001 eth0:IPADDR -- 192.168.100.152 - node0001 eth0:NETMASK default 255.255.255.0 - node0001 eth0:GATEWAY default 192.168.100.1 - node0001 eth0:TYPE -- -- - node0001 eth0:DEFAULT -- false + # wwctl node list -a n001 + NODE FIELD PROFILE VALUE + ===================================================================================== + n001 Id -- n001 + n001 comment default This profile is automatically included for each node + n001 cluster -- -- + n001 container default sle-micro-5.3 + n001 ipxe -- (default) + n001 runtime -- (generic) + n001 wwinit -- (wwinit) + n001 root -- (initramfs) + n001 discoverable -- -- + n001 init -- (/sbin/init) + n001 asset -- -- + n001 kerneloverride -- tw + n001 kernelargs -- (quiet crashkernel=no vga=791 net.naming-scheme=v238) + n001 ipmiaddr -- -- + n001 ipminetmask -- -- + n001 ipmiport -- -- + n001 ipmigateway -- -- + n001 ipmiuser -- -- + n001 ipmipass -- -- + n001 ipmiinterface -- -- + n001 ipmiwrite -- -- + n001 profile -- default + n001 default:type -- (ethernet) + n001 default:onboot -- -- + n001 default:netdev -- eth0 + n001 default:hwaddr -- 11:22:33:44:55:66 + n001 default:ipaddr -- 10.0.2.1 + n001 default:ipaddr6 -- -- + n001 default:netmask -- 255.255.252.0 + n001 default:gateway -- -- + n001 default:mtu -- -- + n001 default:primary -- true Review Only IPMI Settings ------------------------- -The above views show you everything that is set on a Profile or Node level. That is a lot of detail. If you want to view key IPMI connecton details for a node or all nodes, you can do the following. +The above views show you everything that is set on a Profile or Node +level. That is a lot of detail. If you want to view key IPMI connecton +details for a node or all nodes, you can do the following. -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node list -i + # wwctl node list -i + NODE NAME IPMI IPADDR IPMI PORT IPMI USERNAME IPMI INTERFACE + ================================================================================================== + n001 192.168.1.11 -- hwadmin -- + n002 192.168.1.12 -- hwadmin -- + n003 192.168.1.13 -- hwadmin -- + n004 192.168.1.14 -- hwadmin -- - NODE NAME IPMI IPADDR IPMI PORT IPMI USERNAME IPMI PASSWORD IPMI INTERFACE - ============================================================================================================ - node0001 192.168.99.10 -- admin supersecret lanplus - node0002 192.168.99.11 -- admin supersecret lanplus - node0003 192.168.99.12 -- admin supersecret lanplus Power Commands ============== -The ``power`` command can be used to manage the current power state of your nodes through IPMI. +The ``power`` command can be used to manage the current power state of +your nodes through IPMI. ``wwctl power [command]`` where ``[command]`` is one of: @@ -140,6 +172,9 @@ status Console ======= -If your node is setup to use serial over lan (SOL), Warewulf can connect a console to the node. +If your node is setup to use serial over lan (SOL), Warewulf can +connect a console to the node. -``sudo wwctl node console node0001`` \ No newline at end of file +.. code-block:: console + + # wwctl node console n001 diff --git a/userdocs/contents/kernel.rst b/userdocs/contents/kernel.rst index 0bde16e2..05bde261 100644 --- a/userdocs/contents/kernel.rst +++ b/userdocs/contents/kernel.rst @@ -5,49 +5,67 @@ Kernel Management Node Kernels ============ -Warewulf nodes require a Linux kernel to boot. There are multiple ways to do this, but the default, and easiest way is to install the kernel you wish to use for a particular container, into the container. +Warewulf nodes require a Linux kernel to boot. There are multiple ways +to do this, but the default, and easiest way is to install the kernel +you wish to use for a particular container, into the container. -Warewulf will locate the kernel automatically within the container and by default use that kernel for any node configured to use that container image. +Warewulf will locate the kernel automatically within the container and +by default use that kernel for any node configured to use that +container image. -You can see what kernel is included in a container by using the ``wwctl container list`` command: +You can see what kernel is included in a container by using the +``wwctl container list`` command: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl container list + # wwctl container list CONTAINER NAME NODES KERNEL VERSION alpine 0 rocky 0 4.18.0-348.12.2.el8_5.x86_64 rocky_updated 1 4.18.0-348.23.1.el8_5.x86_64 -Here you will notice the alpine contianer that was imported has no kernel within it, and each of the rocky containers include a kernel. +Here you will notice the alpine contianer that was imported has no +kernel within it, and each of the rocky containers include a kernel. -This model was introduced in Warewulf 4.3.0. Previously, Warewulf managed the kernel and the container separately, which made it hard to build and distribute containers that have custom drivers and/or configurations included (e.g. OFED, GPUs, etc.). +This model was introduced in Warewulf 4.3.0. Previously, Warewulf +managed the kernel and the container separately, which made it hard to +build and distribute containers that have custom drivers and/or +configurations included (e.g. OFED, GPUs, etc.). Kernel Overrides ================ -It is still possible to specify a kernel to a container if it doesn't include it's own kernel, or if you wish to override the default kernel by using the ``kernel override`` capability. +It is still possible to specify a kernel to a container if it doesn't +include it's own kernel, or if you wish to override the default kernel +by using the ``kernel override`` capability. -You can specify this option either within the ``nodes.conf`` directly or via the command line with the ``--kerneloverride`` option to ``wwctl node set`` or ``wwctl profile set`` commands. +You can specify this option either within the ``nodes.conf`` directly +or via the command line with the ``--kerneloverride`` option to +``wwctl node set`` or ``wwctl profile set`` commands. -In this case you will also need to import a kernel specifically into Warewulf for this purpose using the ``wwctl kernel import`` command as follows: +In this case you will also need to import a kernel specifically into +Warewulf for this purpose using the ``wwctl kernel import`` command as +follows: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl kernel import $(uname -r) + # wwctl kernel import $(uname -r) 4.18.0-305.3.1.el8_4.x86_64: Done -This process will import not only the kernel image itself, but also all of the kernel modules and firmware associated to this kernel. +This process will import not only the kernel image itself, but also +all of the kernel modules and firmware associated to this kernel. Listing All Imported Kernels ---------------------------- -Once the kernel has been imported, you can list them all with the following command: +Once the kernel has been imported, you can list them all with the +following command: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl kernel list + # wwctl kernel list VNFS NAME NODES 4.18.0-305.3.1.el8_4.x86_64 0 -Once a kernel has been imported and showing up in this list you can configure it to boot compute nodes. \ No newline at end of file +Once a kernel has been imported and showing up in this list you can +configure it to boot compute nodes. diff --git a/userdocs/contents/nodeconfig.rst b/userdocs/contents/nodeconfig.rst index bd751dc2..067ede89 100644 --- a/userdocs/contents/nodeconfig.rst +++ b/userdocs/contents/nodeconfig.rst @@ -6,9 +6,9 @@ The Node Configuration DB ========================= As mentioned in the [Configuration](configuration) section, node -configs are persisted to the ``nodes.conf`` YAML file, but generally it -is best not to edit this file directly (however that is supported, it -is just prone to errors). +configs are persisted to the ``nodes.conf`` YAML file, but generally +it is best not to edit this file directly (however that is supported, +it is just prone to errors). This method of using a YAML configuration file as a backend datastore is both scalable and very lightweight. We've tested this out to over @@ -20,10 +20,33 @@ Adding a New Node Creating a new node is as simple as running the following command: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node add n0000 - Added node: n0000 + # wwctl node add n001 -I 172.16.1.11 + Added node: n001 + +Adding several nodes +-------------------- + +Several nodes can be added with a single command if a node range is +given. An additional IP address will incremented. So the command + +.. code-block:: console + + # wwctl node add n00[2-4] -I 172.16.1.12 + Added node: n002 + Added node: n003 + Added node: n004 + + # wwctl node list -n n00[1-4] + NODE NAME NAME HWADDR IPADDR GATEWAY DEVICE + ========================================================================================== + n001 default -- 172.16.1.11 -- (eth0) + n002 default -- 172.16.1.12 -- (eth0) + n003 default -- 172.16.1.13 -- (eth0) + n004 default -- 172.16.1.14 -- (eth0) + +has added 4 nodes with the incremented IP addresses. Node Names ---------- @@ -32,7 +55,7 @@ For small clusters, you can use simple names (e.g. ``n0000``); but for larger, more complicated clusters that are comprised of multiple clusters and roles it is highly recommended to use node names that include a cluster descriptor. In Warewulf, this is generally done by -using a domain name (e.g. ``n0000.cluster01``). Warewulf will +using a domain name (e.g. ``n001.cluster01``). Warewulf will automatically assume that the domain is the equivalent of the cluster name. @@ -47,45 +70,58 @@ Listing Nodes Once you have configured one or more nodes, you can list them and their attributes as follows: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node list - NODE NAME PROFILES NETWORK - ================================================================================ - n0000 default + # wwctl node list + NODE NAME PROFILES NETWORK + ================================================================================ + n001 default -You can also see the node's full attribute list by specifying the ``-a`` -option (all): +You can also see the node's full attribute list by specifying the +``-a`` option (all): .. code-block:: bash - $ sudo wwctl node list -a - ################################################################################ - NODE FIELD PROFILE VALUE - n0000 Id -- n0000 - n0000 Comment default This profile is automatically included for each node - n0000 Cluster -- -- - n0000 Profiles -- default - n0000 Discoverable -- false - n0000 Container -- -- - n0000 KernelOverride -- -- - n0000 KernelArgs -- (quiet crashkernel=no vga=791 rootfstype=rootfs) - n0000 RuntimeOverlay -- (default) - n0000 SystemOverlay -- (default) - n0000 Ipxe -- (default) - n0000 Init -- (/sbin/init) - n0000 Root -- (initramfs) - n0000 IpmiIpaddr -- -- - n0000 IpmiNetmask -- -- - n0000 IpmiPort -- -- - n0000 IpmiGateway -- -- - n0000 IpmiUserName -- -- - n0000 IpmiInterface -- -- - n0000 IpmiWrite -- -- + # wwctl node list -a n001 + NODE FIELD PROFILE VALUE + ===================================================================================== + n001 Id -- n001 + n001 comment default This profile is automatically included for each node + n001 cluster -- -- + n001 container default sle-micro-5.3 + n001 ipxe -- (default) + n001 runtime -- (generic) + n001 wwinit -- (wwinit) + n001 root -- (initramfs) + n001 discoverable -- -- + n001 init -- (/sbin/init) + n001 asset -- -- + n001 kerneloverride -- -- + n001 kernelargs -- (quiet crashkernel=no vga=791 net.naming-scheme=v238) + n001 ipmiaddr -- -- + n001 ipminetmask -- -- + n001 ipmiport -- -- + n001 ipmigateway -- -- + n001 ipmiuser -- -- + n001 ipmipass -- -- + n001 ipmiinterface -- -- + n001 ipmiwrite -- -- + n001 profile -- default + n001 default:type -- (ethernet) + n001 default:onboot -- -- + n001 default:netdev -- (eth0) + n001 default:hwaddr -- -- + n001 default:ipaddr -- 172.16.1.11 + n001 default:ipaddr6 -- -- + n001 default:netmask -- (255.255.255.0) + n001 default:gateway -- -- + n001 default:mtu -- -- + n001 default:primary -- true .. note:: - The attribute values in parenthesis are default values and can - be overridden in the next section, granted, the default values are + + The attribute values in parenthesis are default values and can be + overridden in the next section, granted, the default values are generally usable. Setting Node Attributes @@ -97,23 +133,23 @@ are a kernel and container, and for that node to be useful, we will also need to configure the network so the nodes are reachable after they boot. -Node configurations are set using the ``wwctl node set`` command. To see -a list of all configuration attributes, use the command ``wwctl node -set --help``. +Node configurations are set using the ``wwctl node set`` command. To +see a list of all configuration attributes, use the command ``wwctl +node set --help``. Configuring the Node's Container Image ====================================== -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node set --container rocky-8 n0000 + # wwctl node set --container rocky-8 n001 Are you sure you want to modify 1 nodes(s): y -And you can check that the container name is set for ``n0000``: +And you can check that the container name is set for ``n001``: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node list -a n0000 | grep Container + # wwctl node list -a n001 | grep Container n0000 Container -- rocky-8 Configuring the Node's Kernel @@ -121,15 +157,21 @@ Configuring the Node's Kernel While the recommended method for assigning a kernel in 4.3 and beyond is to include it in the container / node image, a kernel can still be -specified as an override at the node or profile. +specified as an override at the node or profile. To illustrate this, +we import the most recent kernel from a openSUSE Tumbleweed release. -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node set --kerneloverride $(uname -r) n0000 - Are you sure you want to modify 1 nodes(s): y + # wwctl container import docker://registry.opensuse.org/science/warewulf/tumbleweed/containerfile/kernel:latest tw + # wwctl kernel import -DC tw + # wwctl kernel list + KERNEL NAME KERNEL VERSION NODES + tw 6.1.10-1-default 0 + # wwctl node set --kerneloverride tw n001 + Are you sure you want to modify 1 nodes(s): y - $ sudo wwctl node list -a n0000 | grep KernelOverride - n0000 KernelOverride -- 4.18.0-305.3.1.el8_4.x86_64 + # wwctl node list -a n001 | grep kerneloverride + n001 kerneloverride -- tw Configuring the Node's Network ------------------------------ @@ -137,45 +179,56 @@ Configuring the Node's Network To configure the network, we have to pick a network device name and provide the network information as follows: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node set --netdev eth0 --hwaddr 11:22:33:44:55:66 --ipaddr 10.0.2.1 --netmask 255.255.252.0 n0000 + # wwctl node set --netdev eth0 --hwaddr 11:22:33:44:55:66 --ipaddr 10.0.2.1 --netmask 255.255.252.0 n001 Are you sure you want to modify 1 nodes(s): y You can now see that the node contains configuration attributes for container, kernel, and network: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node list -a n0000 - ################################################################################ - NODE FIELD PROFILE VALUE - n0000 Id -- n0000 - n0000 Comment default This profile is automatically included for each node - n0000 Cluster -- -- - n0000 Profiles -- default - n0000 Discoverable -- false - n0000 Container -- rocky-8 - n0000 Kernel -- 4.18.0-305.3.1.el8_4.x86_64 - n0000 KernelArgs -- (quiet crashkernel=no vga=791 rootfstype=rootfs) - n0000 RuntimeOverlay -- (default) - n0000 SystemOverlay -- (default) - n0000 Ipxe -- (default) - n0000 Init -- (/sbin/init) - n0000 Root -- (initramfs) - n0000 IpmiIpaddr -- -- - n0000 IpmiNetmask -- -- - n0000 IpmiPort -- -- - n0000 IpmiGateway -- -- - n0000 IpmiUserName -- -- - n0000 IpmiInterface -- -- - n0000 default:DEVICE -- eth0 - n0000 default:HWADDR -- 11:22:33:44:55:66 - n0000 default:IPADDR -- 10.0.2.1 - n0000 default:NETMASK -- 255.255.252.0 - n0000 default:GATEWAY -- -- - n0000 default:TYPE -- -- - n0000 default:DEFAULT -- false + # wwctl node list -a n001 + ===================================================================================== + n001 Id -- n001 + n001 comment default This profile is automatically included for each node + n001 cluster -- -- + n001 container default sle-micro-5.3 + n001 ipxe -- (default) + n001 runtime -- (generic) + n001 wwinit -- (wwinit) + n001 root -- (initramfs) + n001 discoverable -- -- + n001 init -- (/sbin/init) + n001 asset -- -- + n001 kerneloverride -- tw + n001 kernelargs -- (quiet crashkernel=no vga=791 net.naming-scheme=v238) + n001 ipmiaddr -- -- + n001 ipminetmask -- -- + n001 ipmiport -- -- + n001 ipmigateway -- -- + n001 ipmiuser -- -- + n001 ipmipass -- -- + n001 ipmiinterface -- -- + n001 ipmiwrite -- -- + n001 profile -- default + n001 default:type -- (ethernet) + n001 default:onboot -- -- + n001 default:netdev -- eth0 + n001 default:hwaddr -- 11:22:33:44:55:66 + n001 default:ipaddr -- 10.0.2.1 + n001 default:ipaddr6 -- -- + n001 default:netmask -- 255.255.252.0 + n001 default:gateway -- -- + n001 default:mtu -- -- + n001 default:primary -- true + + # wwctl node set --cluster cluster01 n001 + Are you sure you want to modify 1 nodes(s): y + + # wwctl node list -a n001 | grep cluster + n001 cluster -- cluster01 Un-setting Node Attributes ========================== @@ -183,20 +236,12 @@ Un-setting Node Attributes If you wish to ``unset`` a particular value, set the value to ``UNDEF``. For example: -.. code-block:: bash - - $ sudo wwctl node set --cluster cluster01 n0000 - Are you sure you want to modify 1 nodes(s): y - - $ sudo wwctl node list -a n0000 | grep Cluster - n0000 Cluster -- cluster01 - And to unset this configuration attribute: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node set --cluster UNDEF n0000 + # wwctl node set --cluster UNDEF n001 Are you sure you want to modify 1 nodes(s): y - $ sudo wwctl node list -a n0000 | grep Cluster - n0000 Cluster -- -- \ No newline at end of file + # wwctl node list -a n001 | grep Cluster + n001 Cluster -- -- diff --git a/userdocs/contents/overlays.rst b/userdocs/contents/overlays.rst index 5ebe7829..84f6acd0 100644 --- a/userdocs/contents/overlays.rst +++ b/userdocs/contents/overlays.rst @@ -2,140 +2,255 @@ Warewulf Overlays ================= -So at this point, we have discussed how Warewulf is designed to scalably provision and manage thousands of cluster nodes by utilizing identical stateless boot images. And there-in lies a problem to solve. If these boot images are completely identical, then how do we configure things like hostnames? IP addresses? Or any other node specific custom configurations? +So at this point, we have discussed how Warewulf is designed to +scalably provision and manage thousands of cluster nodes by utilizing +identical stateless boot images. And there-in lies a problem to +solve. If these boot images are completely identical, then how do we +configure things like hostnames? IP addresses? Or any other node +specific custom configurations? -While some of this can be managed by services like DHCP, and other bits by configuration management, which can absolutely be done with Warewulf and many people choose to do, these are heavy-weight solutions to a simple problem to solve. +While some of this can be managed by services like DHCP, and other +bits by configuration management, which can absolutely be done with +Warewulf and many people choose to do, these are heavy-weight +solutions to a simple problem to solve. -Warewulf solves this with overlays and uses overlays in different ways through the provisioning process. Two of these overlays are exposed to the users, the **system overlay** and the **runtime overlay**. +Warewulf solves this with overlays and uses overlays in different ways +through the provisioning process. A node or profile can configure an +overlay in two different ways: -.. note:: - Another overlay that isn't directly exposed is the **kmods overlay** which contains all of the kernel modules to match the configured kernel. Because this overlay is used "behind the scenes" it is outside the scope of this document. +* An overlay can be configured to run during boot as part of the + ``wwinit`` process. These overlays are called **system overlay** or + **wwinit overlays**. +* An overlay can be configured to run periodically while the system is + running. These overlays are called **runtime overlays** or **generic + overlays**. -System Overlay -============== +The default profile has both a **wwinit** and a **runtime** overlay +configured. -The System Overlay is used by the core of Warewulf to setup the environment on the node necessary for provisioning. The default system overlay is called ``wwinit``. Generally speaking, it will not be necessary to make changes to this overlay, but it is possible to change or configure this overlay to meet site specific needs if necessary. +Overlays are compiled for each compute node individually. -Runtime Overlay -=============== +Defined Overlays +================ -The Runtime Overlay is the overlay that is responsible for most of the typical system administration configurations. Here you will make changes necessary to support your operating system as well as application configurations. +System or wwinit overlay +------------------------ -Once the system is provisioned and booted, the ``wwclient`` program (which is provisioned as part of the ``wwinit`` system overlay) will continuously update the node with updates in the runtime overlay. +This overlay contains all the nesscesary scripts to provision a +Warewulf node. It is available before the ``systemd`` or other init is +called and contains all configurations which are needed to bring up +the compute node. It is not updated during run time. Besides the +network configurations for + +* wicked +* NetworkManager +* EL legacy network scripts + +it also contains udev rules, which will set the interface name of the +first network device to ``eth0``. Before the ``systemd`` init is +called, the overlay loops through the scripts in +``/wwinit/warwulf/init.d/*`` which will setup + +* Ipmi +* wwclient +* selinux + +Runtime Overlay or generic Overlay +---------------------------------- + +The runtime overlay is updated by the ``wwclient`` service on a +regular basis (by default, once per minute). In the standard +configuration it includes updates for ``/etc/passwd``, ``/etc/group`` +and ``/etc/hosts``. Additionally the ``authorized_keys`` file of the +root user is updated. It is recommended to use this overlay for +dynamic configuration files like ``slurm.conf``. Once the system is +provisioned and booted, the ``wwclient`` program (which is provisioned +as part of the ``wwinit`` system overlay) will continuously update the +node with updates in the runtime overlay. + +Host Overlay +------------ + +Configuration files used for the configuration of the Warewulf host / +server are stored in the **host** overlay. Unlike other overlays, it +*must* have the name ``host``. Existing files on the host are copied +to backup files with a ``wwbackup`` suffix at the first +run. (Subsequent use of the host overlay won't overwrite existing +``wwbackup`` files.) + +The following services get configuration files via the host overlay: + +* ssh keys are created with the scrips ``ssh_setup.sh`` and + ``ssh_setup.csh`` +* hosts entries are created by manipulating ``/etc/hosts`` with the + template ``hosts.ww`` +* nfs kernel server receives its exports from the template + ``exports.ww`` +* the dhcpd service is configured with ``dhcpd.conf.ww`` + +Combining Overlays +================== + +When changing the overlays, it is recommended not to change them, but +to add the changed files to a new overlay and combine them in the +configuration. This is possible as the configuration fields for the +**wwinit** and **runtime** overlays are lists and can contain several +overlays. As an example for this, we will overwrite the +``/etc/issue`` file from the **wwinit** overlay. For this we will +create a new overlay called welcome and import the file ``/etc/issue`` +from the host to it. This overlay is then combined with the existing +**wwinit** overlay. + +.. code-block:: console + + # wwctl overlay create welcome + # wwctl overlay mkdir welcome /etc + # wwctl overlay import welcome /etc/issue + # wwctl profile set default --wwinit=wwinit,welcome + ? Are you sure you want to modify 1 profile(s)? [y/N] y + # wwctl profile list default -a |grep welcome + default SystemOverlay wwinit,welcome Templates ========= -Templates allow you to create dynamic content such that the files downloaded for each node will be customized for that node. Templates allow you to insert everything from variables, to including files from the control node, as well as conditional content and loops. +Templates allow you to create dynamic content such that the files +downloaded for each node will be customized for that node. Templates +allow you to insert everything from variables, to including files from +the control node, as well as conditional content and loops. -Warewulf uses the ``text/template`` engine to facilitate implementing dynamic content in a simple and standardized manner. +Warewulf uses the ``text/template`` engine to facilitate implementing +dynamic content in a simple and standardized manner. -All template files will end with the suffix of ``.ww``. That tells Warewulf that when building a file, that it should parse that file as a template. When it does that, the resulting file is static and can have node customizations that are obtained from the node configuration attributes. +All template files will end with the suffix of ``.ww``. That tells +Warewulf that when building a file, that it should parse that file as +a template. When it does that, the resulting file is static and can +have node customizations that are obtained from the node configuration +attributes. .. note:: - When the file is persisted within the built overlay, the ``.ww`` will be dropped, so ``/etc/hosts.ww`` will end up being ``/etc/hosts``. + + When the file is persisted within the built overlay, the ``.ww`` + will be dropped, so ``/etc/hosts.ww`` will end up being + ``/etc/hosts``. Using Overlays ============== -Warewulf includes a command group for manipulating overlays (``wwctl overlay``). With this you can add, edit, remove, change ownership, permissions, etc. +Warewulf includes a command group for manipulating overlays (``wwctl +overlay``). With this you can add, edit, remove, change ownership, +permissions, etc. -The general syntax is as follows: +.. + note:: + There is now possibility to delete files with an overlay! [example needed] -.. code-block:: bash +Build +----- - wwctl overlay [action] [overlay name] ... +.. code-block:: console -* **action**: the overlay subcommand you are invoking -* **overlay name**: the name of the overlay in question within a given type -* **...**: additional arguments are action specific + wwctl overlay build [-H,--hosts|-N,--nodes|-o,--output directory|-O,--overlay-name] nodepattern -By default there is one overlay in each of the system and runtime overlay types. Both overlays are called "default". To say it differently, there are two default overlays, one is a system overlay and one is a runtime overlay. +Without any arguments the command will interpret the templates for all +overlays for every compute node and also all the templates in the host +overlay. For every overlay of the compute nodes a gzip compressed cpio +archive is created. The range of the nodes can be restricted as the +last argument. With the ``-H`` flag only the host overlay is +built. With the ``-N`` flag only compute node overlays are +built. Specific overlays can be selected with ``-O`` flag. For +debugging purposes the templates can be written to a directory given +via the ``-o`` flag. -Viewing the Files Within an Overlay -=================================== +By default Warewulf will build/update and cache overlays as needed +(configurable in the ``warewulf.conf``). -Overlays can be viewed with the command ``wwctl overlay list``. You can see the files within an overlay by adding the ``-a`` or ``-l`` options as follows: +Chmod +----- -.. code-block:: bash +.. code-block:: console - $ sudo wwctl overlay list -l generic - PERM MODE UID GID OVERLAY FILE PATH - -rwxr-xr-x 0 0 generic /etc/ - -rw-r--r-- 0 0 generic /etc/group.ww - -rw-r--r-- 0 0 generic /etc/hosts.ww - -rw-r--r-- 0 0 generic /etc/passwd.ww - -rwxr-xr-x 0 0 generic /root/ - -rwxr-xr-x 0 0 generic /root/.ssh/ - -rw-r--r-- 0 0 generic /root/.ssh/authorized_keys.ww + wwctl overlay chmod overlay-name filename mode -Creating a New File Within an Overlay -===================================== +This subcommand changes the permissions of a single file within an +overlay. You can use any mode format supported by the chmod command. -Just like any file on the system, you can create and edit a file at the same time. So to do that, you simple ``edit`` a new file as follows: +Chown +----- -.. code-block:: bash +.. code-block:: console - $ sudo wwctl overlay edit [overlay name] [file path] + wwctl overlay chown overlay-name filename UID [GID] -For example: +With this command you can change the ownership of a file within a +given overlay to the user specified by UID. Optionally, it will also +change group ownership to GID. -.. code-block:: bash +Create +------ - $ sudo wwctl overlay edit generic /etc/testfile +.. code-block:: console -and you can validate that the file is there with the ``list`` command: + wwctl overlay create overlay-name -.. code-block:: bash +This command creates a new empty overlay with the given name. - $ sudo wwctl overlay list generic -l - PERM MODE UID GID RUNTIME-OVERLAY FILE PATH - -rwxr-xr-x 0 0 generic /etc/ - -rw-r--r-- 0 0 generic /etc/group.ww - -rw-r--r-- 0 0 generic /etc/hosts.ww - -rw-r--r-- 0 0 generic /etc/passwd.ww - -rwxr-xr-x 0 0 generic /etc/testfile - -rwxr-xr-x 0 0 generic /root/ - -rwxr-xr-x 0 0 generic /root/.ssh/ - -rw-r--r-- 0 0 generic /root/.ssh/authorized_keys.ww +Delete +------ -.. note:: - To create a template file, simply name the file with the suffix ``.ww``. This suffix will tell Warewulf that the file should be parsed by the templating engine and written into the overlay with the suffix stripped off. +.. code-block:: console -Building Overlays -================= + wwctl overlay delete [-f,--force] overlay-name [File [File ...]] -By default Warewulf will build/update and cache overlays as needed (configurable in the ``warewulf.conf``). +Either the given overlay is deleted (must be empty or use the +``--force`` flag) or the specified file within the overlay is +deleted. With the ``--parents`` flag the directory of the deleted file +is also removed if no other file is in the directory. -You can however build overlays by hand, and in some cases this will be advantageous (like if you are freshly booting thousands of compute nodes in parallel). The command to do that is: +Edit +---- +.. code-block:: console -.. code-block:: bash + wwctl overlay edit [--mode,-m MODE|--parents,-p] overlay-name file - # wwctl overlay build n00[00-10] - Building overlays for n0000: [wwinit, generic] - Building overlays for n0001: [wwinit, generic] - Building overlays for n0002: [wwinit, generic] - Building overlays for n0003: [wwinit, generic] - Building overlays for n0004: [wwinit, generic] - Building overlays for n0005: [wwinit, generic] - Building overlays for n0006: [wwinit, generic] - Building overlays for n0007: [wwinit, generic] - Building overlays for n0008: [wwinit, generic] - Building overlays for n0009: [wwinit, generic] - Building overlays for n0010: [wwinit, generic] +Use this command to edit an existing or a new file in the given +overlay. If a the new file ends with a ``.ww`` suffix an example +template header is added to the file. With the ``--parents`` flag +necessary parent directories for a new file are created. -Other Overlay Actions -===================== +Import +------ +.. code-block:: console -Warewulf includes a number of overlay action commands to interact with the overlays in a programmatic and controlled manner. All of the commands use very similar usage structure and work as the above examples do. A summary of all of the overlay actions are as follows: + wwctl overlay import [--mode,-m|--noupdate,-n] overlay-name file-name [new-file-name] -* **build**: (Re)build an overlay -* **chmod**: Change file permissions within an overlay -* **chown**: Change file ownership within an overlay -* **create**: Initialize a new Overlay -* **delete**: Delete Warewulf Overlay or files -* **edit**: Edit/Create a file within a Warewulf Overlay -* **import**: Import a file into a Warewulf Overlay -* **list**: List Warewulf Overlays and files -* **mkdir**: Create a new directory within an Overlay -* **show**: Show (cat) a file within a Warewulf Overlay \ No newline at end of file +The given file is imported to the overlay. If no new-file-name is +given, the file will be placed in the overlay at the same path as on +the host. With the ``--noupdate`` flag you can block the rebuild of +the overlays. + +List +---- + +.. code-block:: console + + wwctl overlay list [--all,-a|--long,-l] [overlay-name] + +With this command all existing overlays and files in them can be +listed. Without any option only the overlay names and their number of +files are listed. With the ``--all`` switch also the every file is +shown. The ``--long`` option will also display the permissions, UID, +and GID of each file. + +Show +---- + +.. code-block:: console + + wwctl overlay show [--quiet,-q|--render,-r nodename] overlay-name file + +The content of the file for the given overlay is displayed with this +command. With the ``--render`` option a template is rendered as it +will be rendered for the given node. The node name is a mandatory +argument to the ``--render`` flag. Additional information for the file +can be suppressed with the ``--quiet`` option. diff --git a/userdocs/contents/profiles.rst b/userdocs/contents/profiles.rst index 2e9250d4..0f3a2eae 100644 --- a/userdocs/contents/profiles.rst +++ b/userdocs/contents/profiles.rst @@ -1,28 +1,41 @@ -############# +============= Node Profiles -############# +============= -Profiles provide a way to scalably group node configurations together. Instead of redundant configurations for each node, you can put that into a profile and the nodes will inherit these configurations. This is very handy if you have groups of node specific customizations. For example, a few hundred nodes that are running a particular container or kernel, and another group of nodes that are running a different kernel or container. +Profiles provide a way to scalably group node configurations +together. Instead of redundant configurations for each node, you can +put that into a profile and the nodes will inherit these +configurations. This is very handy if you have groups of node specific +customizations. For example, a few hundred nodes that are running a +particular container or kernel, and another group of nodes that are +running a different kernel or container. -Any node configuration attributes can be applied to a profile, but there are always going to be some node configurations which must be specific to a node, like a network HW/MAC address or an IP address. +Any node configuration attributes can be applied to a profile, but +there are always going to be some node configurations which must be +specific to a node, like a network HW/MAC address or an IP address. An Introduction To Profiles -============================== +=========================== -Every new node is automatically added to a profile called ``default``. You can view the configuration attributes of this profile by using the ``wwctl profile list`` command. Like the ``wwctl node list`` command, this will provide a summary, but you can see **all** configuration attributes by using the ``--all`` or ``-a`` flag as follows: +Every new node is automatically added to a profile called +``default``. You can view the configuration attributes of this profile +by using the ``wwctl profile list`` command. Like the ``wwctl node +list`` command, this will provide a summary, but you can see **all** +configuration attributes by using the ``--all`` or ``-a`` flag as +follows: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl profile list + # wwctl profile list PROFILE NAME COMMENT/DESCRIPTION ================================================================================ default This profile is automatically included for each node And with the ``-a`` flag: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl profile list -a + # wwctl profile list -a ################################################################################ PROFILE NAME FIELD VALUE default Id default @@ -43,55 +56,65 @@ And with the ``-a`` flag: default IpmiUserName -- default IpmiInterface -- -As you can see here, there is only one attribute set by default in this profile, and that is the "Comment" field. That Comment is inherited by any nodes that are configured to use this profile. So if we look at the node we configured in the last section, we can see that configuration attribute there: +As you can see here, there is only one attribute set by default in +this profile, and that is the "Comment" field. That Comment is +inherited by any nodes that are configured to use this profile. So if +we look at the node we configured in the last section, we can see that +configuration attribute there: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node list -a | head -n 5 + # wwctl node list -a | head -n 5 ################################################################################ NODE FIELD PROFILE VALUE n0000 Id -- n0000 n0000 Comment default This profile is automatically included for each node n0000 Cluster -- -- -Here you can see that the "Comment" attribute was inherited by this node, and it also provides you with the information of which profile this attribute was inherited from. This is very useful information as nodes can be part of multiple profiles with inheritance being cascading. +Here you can see that the "Comment" attribute was inherited by this +node, and it also provides you with the information of which profile +this attribute was inherited from. This is very useful information as +nodes can be part of multiple profiles with inheritance being +cascading. Multiple Profiles ================= -For demonstration purposes, let's create another profile and demonstrate how to use this second profile. +For demonstration purposes, let's create another profile and +demonstrate how to use this second profile. -.. code-block:: bash +.. code-block:: console - $ sudo wwctl profile add test_profile - $ sudo wwctl profile list + # wwctl profile add test_profile + # wwctl profile list PROFILE NAME COMMENT/DESCRIPTION ================================================================================ default This profile is automatically included for each node test_profile -- -Now that we've created a new profile, let's create a configuration attribute in this profile: +Now that we've created a new profile, let's create a configuration +attribute in this profile: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl profile set --cluster cluster01 test_profile - ? Are you sure you want to modify 1 profile(s)? [y/N] y█ + # wwctl profile set --cluster cluster01 test_profile + ? Are you sure you want to modify 1 profile(s)? [y/N] y - $ sudo wwctl profile list -a test_profile | grep Cluster + # wwctl profile list -a test_profile | grep Cluster test_profile Cluster cluster01 Lastly we just need to configure this profile to our node(s): -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node set --addprofile test_profile n0000 + # wwctl node set --addprofile test_profile n0000 Are you sure you want to modify 1 nodes(s): y And you can now verify that the node has both profile configurations: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node list -a | head -n 6 + # wwctl node list -a | head -n 6 ################################################################################ NODE FIELD PROFILE VALUE n0000 Id -- n0000 @@ -102,14 +125,15 @@ And you can now verify that the node has both profile configurations: Cascading Profiles ================== -In the previous example, we set a single node to have two profile configurations. We can also overwrite configurations as follows: +In the previous example, we set a single node to have two profile +configurations. We can also overwrite configurations as follows: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl profile set --comment "test comment" test_profile + # wwctl profile set --comment "test comment" test_profile Are you sure you want to modify 1 profile(s): y - $ sudo wwctl node list -a | head -n 6 + # wwctl node list -a | head -n 6 ################################################################################ NODE FIELD PROFILE VALUE n0000 Id -- n0000 @@ -117,14 +141,15 @@ In the previous example, we set a single node to have two profile configurations n0000 Cluster test_profile cluster01 n0000 Profiles -- default,test_profile -And if we delete the superseded profile attribute from ``test_profile`` we can now see the previous configuration: +And if we delete the superseded profile attribute from +``test_profile`` we can now see the previous configuration: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl profile set --comment UNDEF test_profile + # wwctl profile set --comment UNDEF test_profile Are you sure you want to modify 1 profile(s): y - $ sudo wwctl node list -a | head -n 6 + # wwctl node list -a | head -n 6 ################################################################################ NODE FIELD PROFILE VALUE n0000 Id -- n0000 @@ -132,19 +157,24 @@ And if we delete the superseded profile attribute from ``test_profile`` we can n n0000 Cluster test_profile cluster01 n0000 Profiles -- default,test_profile -This is a very useful feature for dealing with many groups of cluster nodes and/or testing new configurations on smaller subsets of cluster nodes. For example, you can use this method to run a different kernel on only a subset or group of cluster nodes without changing any other node attributes. +This is a very useful feature for dealing with many groups of cluster +nodes and/or testing new configurations on smaller subsets of cluster +nodes. For example, you can use this method to run a different kernel +on only a subset or group of cluster nodes without changing any other +node attributes. Overriding Profiles =================== -All profile configurations can be overwritten by a node configuration as can be seen here: +All profile configurations can be overwritten by a node configuration +as can be seen here: -.. code-block:: bash +.. code-block:: console - $ sudo wwctl node set --comment "This value takes precedent" n0000 + # wwctl node set --comment "This value takes precedent" n0000 Are you sure you want to modify 1 nodes(s): y - $ sudo wwctl node list -a | head -n 6 + # wwctl node list -a | head -n 6 ################################################################################ NODE FIELD PROFILE VALUE n0000 Id -- n0000 @@ -155,8 +185,19 @@ All profile configurations can be overwritten by a node configuration as can be How To Use Profiles Effectively =============================== -There are a lot of ways to use profiles to facilitate the management of large cluster node attributes, but there is nothing inherent in the design of Warewulf that requires use of them for anything. It is completely reasonable to not use profiles at all to help with node configuration attributes. +There are a lot of ways to use profiles to facilitate the management +of large cluster node attributes, but there is nothing inherent in the +design of Warewulf that requires use of them for anything. It is +completely reasonable to not use profiles at all to help with node +configuration attributes. -But if you do wish to use profiles, the best way to use them is to manage "fixed" configurations of groups of cluster nodes. For example, if you have multiple sub-clusters in your cluster, it might be advantageous to have a ``cluster_name`` profile which includes things like network configurations, and/or a specific kernel, container, boot arguments, etc. +But if you do wish to use profiles, the best way to use them is to +manage "fixed" configurations of groups of cluster nodes. For example, +if you have multiple sub-clusters in your cluster, it might be +advantageous to have a ``cluster_name`` profile which includes things +like network configurations, and/or a specific kernel, container, boot +arguments, etc. -Node specific information, like HW/MAC addresses and IP addresses should always be put in a node configuration rather than a profile configuration. \ No newline at end of file +Node specific information, like HW/MAC addresses and IP addresses +should always be put in a node configuration rather than a profile +configuration. diff --git a/userdocs/contents/provisioning.rst b/userdocs/contents/provisioning.rst index f567cada..8c69f620 100644 --- a/userdocs/contents/provisioning.rst +++ b/userdocs/contents/provisioning.rst @@ -7,37 +7,66 @@ Once the nodes are configured in Warewulf, they are ready to boot. Node Hardware Setup =================== -The only thing that Warewulf requires to provision is that the node is set to PXE boot. You may need to change the boot order if there is a local disk present and bootable. This is a configuration change you will have to make in the BIOS of the cluster node. +The only thing that Warewulf requires to provision is that the node is +set to PXE boot. You may need to change the boot order if there is a +local disk present and bootable. This is a configuration change you +will have to make in the BIOS of the cluster node. -Each vendor does this differently and as a result we won't go into the setup specifics here and if you can not find information on how to PXE boot your nodes, please contact your hardware vendor support. +Each vendor does this differently and as a result we won't go into the +setup specifics here and if you can not find information on how to PXE +boot your nodes, please contact your hardware vendor support. .. note:: - If you find that you are going to use Warewulf, or any other cluster provisioning tool, it is very helpful to require that hardware vendors preconfigure your cluster nodes with values of your choosing, and ask them to provide a text file that includes all of the HW/MAC addresses of the compute nodes in the order they are racked (which most creditable vendors will do). You can also ask them to certify their computing stack for the operating system you wish to use and the provisioning system. This helps hardware vendors to ensure their stack works with open source projects like Warewulf, Debian, OpenSuSE, and Rocky Linux. + + If you find that you are going to use Warewulf, or any other + cluster provisioning tool, it is very helpful to require that + hardware vendors preconfigure your cluster nodes with values of + your choosing, and ask them to provide a text file that includes + all of the HW/MAC addresses of the compute nodes in the order they + are racked (which most creditable vendors will do). You can also + ask them to certify their computing stack for the operating system + you wish to use and the provisioning system. This helps hardware + vendors to ensure their stack works with open source projects like + Warewulf, Debian, OpenSuSE, and Rocky Linux. The Provisioning Process ======================== -When the cluster node boots, the following order of operations will occur: +When the cluster node boots, the following order of operations will +occur: #. BIOS: - #. The system BIOS will bootstrap the initialization of the hardware + #. The system BIOS will bootstrap the initialization of the + hardware #. The network card will register its option ROM into the BIOS - #. The BIOS will run through all of its functions and finish with boot devices + #. The BIOS will run through all of its functions and finish with + boot devices #. The boot devices are attempted in order - #. When it gets to the network boot device, PXE is run from the firmware on the network card + #. When it gets to the network boot device, PXE is run from the + firmware on the network card #. PXE: #. PXE will request a BOOTP/DHCP address on the network - #. The Warewulf controller's DHCP server will respond with a network configuration and filename to try and boot - #. PXE will attempt to download the filename referred to in the DHCP response via TFTP - #. The downloaded file will execute an iPXE stack which will reach out to the Warewulf server for it's configuration + #. The Warewulf controller's DHCP server will respond with a + network configuration and filename to try and boot + #. PXE will attempt to download the filename referred to in the + DHCP response via TFTP + #. The downloaded file will execute an iPXE stack which will reach + out to the Warewulf server for it's configuration #. Bootstrap: - #. The Warewulf server will generate the iPXE configuration which will include directions of what else is necessary to download and how to boot. - #. The kernel, container image, kernel modules, and system overlay are all downloaded over REST HTTP from the Warewulf Server - #. iPXE executes the kernel and processes the overlays to provide a unified root file system - #. Warewulf bootstraps the initialization of cluster node's operating system + #. The Warewulf server will generate the iPXE configuration which + will include directions of what else is necessary to download + and how to boot. + #. The kernel, container image, kernel modules, and system overlay + are all downloaded over REST HTTP from the Warewulf Server + #. iPXE executes the kernel and processes the overlays to provide + a unified root file system + #. Warewulf bootstraps the initialization of cluster node's + operating system #. File System (re)configuration #. SELinux - #. ``wwclient`` is called as a background daemon and sleeps until network is ready + #. ``wwclient`` is called as a background daemon and sleeps + until network is ready #. The Warewulf bootstrap execs the container's ``/sbin/init`` #. Container: - #. The container now boots exactly as any operating system would expect \ No newline at end of file + #. The container now boots exactly as any operating system would + expect diff --git a/userdocs/contents/security.rst b/userdocs/contents/security.rst index 8f2c29b2..1f585de1 100644 --- a/userdocs/contents/security.rst +++ b/userdocs/contents/security.rst @@ -2,35 +2,87 @@ Security ======== -Historically, most HPC clusters utilize a security model that is "hard on the exterior and soft and gushy on the interior". It is not that a user has free roam once logged in, but rather we tend to rely on just simple POSIX security models on the inside. For example, one of the common practices is to completely disable SELinux on a new cluster setup. Just kill it because it gets in the way. +Historically, most HPC clusters utilize a security model that is "hard +on the exterior and soft and gushy on the interior". It is not that a +user has free roam once logged in, but rather we tend to rely on just +simple POSIX security models on the inside. For example, one of the +common practices is to completely disable SELinux on a new cluster +setup. Just kill it because it gets in the way. -For that reason, most critical HPC clusters leverage VPNs and/or bastion hosts with multi-factor authentication (MFA) to help secure it on the outside. But even with MFA and secure ssh connections through a bastion host, it is still possible for malicious users to gain access to these systems. Security being like layers of an onion is accurate, but on an HPC system, those layers are predominately on the outside of the cluster, not the inside. +For that reason, most critical HPC clusters leverage VPNs and/or +bastion hosts with multi-factor authentication (MFA) to help secure it +on the outside. But even with MFA and secure ssh connections through a +bastion host, it is still possible for malicious users to gain access +to these systems. Security being like layers of an onion is accurate, +but on an HPC system, those layers are predominately on the outside of +the cluster, not the inside. -Warewulf was written and designed from the ground up to go a bit further. And while certain parallelization and high performance library capabilities still require lowering the security threshold, Warewulf strives to not be a blocker here. +Warewulf was written and designed from the ground up to go a bit +further. And while certain parallelization and high performance +library capabilities still require lowering the security threshold, +Warewulf strives to not be a blocker here. SELinux ======= -The Warewulf server itself was developed with SELinux enabled in "targeted" and "enforcing" mode and with the firewall active. +The Warewulf server itself was developed with SELinux enabled in +"targeted" and "enforcing" mode and with the firewall active. -Additionally, the provisioning process fully supports SELinux by default. In previous versions you had to enable a switch to support SELinux, but in Warewulf v4 and above, it is always enabled, but you do have to make some configuration changes. +Additionally, the provisioning process fully supports SELinux by +default. In previous versions you had to enable a switch to support +SELinux, but in Warewulf v4 and above, it is always enabled, but you +do have to make some configuration changes. -#. The first thing to do is to change the provision "Root" option. By default this is ``initramfs`` which means, take whatever file system the kernel hands us. By default this is a ``ramfs`` type file system (however this may not always be the case) and this format does not support extended file attributes which are required for SELinux. Instead you must configure Warewulf to use ``tmpfs`` for the provisioning file system. That change is made like: ``$ sudo wwctl profile set --root tmpfs default``. +#. The first thing to do is to change the provision "Root" option. By + default this is ``initramfs`` which means, take whatever file + system the kernel hands us. By default this is a ``ramfs`` type + file system (however this may not always be the case) and this + format does not support extended file attributes which are required + for SELinux. Instead you must configure Warewulf to use ``tmpfs`` + for the provisioning file system. That change is made like: ``$ + sudo wwctl profile set --root tmpfs default``. -#. That is all you have to do to ensure that Warewulf will probably support SELinux. Once that is done, you just need to enable SELinux in ``/etc/sysconfig/selinux`` and install the appropriate profiles into the container. +#. That is all you have to do to ensure that Warewulf will probably + support SELinux. Once that is done, you just need to enable SELinux + in ``/etc/sysconfig/selinux`` and install the appropriate profiles + into the container. Provisioning Security ===================== -Provisioning in generally is known to be rather "insecure" because when a user lands on a compute node, there is generally nothing stopping them from spoofing a provision request and downloading the provisioned raw materials for inspection. +Provisioning in generally is known to be rather "insecure" because +when a user lands on a compute node, there is generally nothing +stopping them from spoofing a provision request and downloading the +provisioned raw materials for inspection. In Warewulf there are two ways to secure the provisioning process: -#. The provisioning connections and transfers are not secure due to not being able to manage a secure root of trust through a PXE process. The best way to secure the provisioning process is to enact a vLAN used specifically for provisioning. Warewulf supports this but you must consult your switch documentation and features to implement a default vLAN for provisioning and ensure that the runtime operating system is configured for a different tagged vLAN once booted. +#. The provisioning connections and transfers are not secure due to + not being able to manage a secure root of trust through a PXE + process. The best way to secure the provisioning process is to + enact a vLAN used specifically for provisioning. Warewulf supports + this but you must consult your switch documentation and features to + implement a default vLAN for provisioning and ensure that the + runtime operating system is configured for a different tagged vLAN + once booted. -#. Warewulf will leverage hardware "asset tags" which almost all vendors support. It is a configurable string that is configured in firmware and accessible only via root or physical access. During provisioning (as well as post provisioning via ``wwclient``) Warewulf, can use the asset tag as a secure token. If you have setup your hardware with an asset tag, you simply need to tell Warewulf what that asset tag is. When the asset tag is defined in Warewulf (``wwctl node set --assetkey "..."``), it will only provision and communicate with requests from that system matching that asset tag. +#. Warewulf will leverage hardware "asset tags" which almost all + vendors support. It is a configurable string that is configured in + firmware and accessible only via root or physical access. During + provisioning (as well as post provisioning via ``wwclient``) + Warewulf, can use the asset tag as a secure token. If you have + setup your hardware with an asset tag, you simply need to tell + Warewulf what that asset tag is. When the asset tag is defined in + Warewulf (``wwctl node set --assetkey "..."``), it will only + provision and communicate with requests from that system matching + that asset tag. Summary ======= -Warewulf does not limit the security posture of a cluster at all, and perhaps it increases it as not all provisioners work with firewalls and SELinux enabled and enforcing. But even with that, cluster security is always up to the system manager and organizational policies. Our job is just to ensure that we don't limit those policies in any way. \ No newline at end of file +Warewulf does not limit the security posture of a cluster at all, and +perhaps it increases it as not all provisioners work with firewalls +and SELinux enabled and enforcing. But even with that, cluster +security is always up to the system manager and organizational +policies. Our job is just to ensure that we don't limit those policies +in any way. diff --git a/userdocs/contents/setup.rst b/userdocs/contents/setup.rst index d75193f0..8eec5193 100644 --- a/userdocs/contents/setup.rst +++ b/userdocs/contents/setup.rst @@ -5,34 +5,69 @@ Control Server Setup Operating System Installation ============================= -Warewulf has almost no predetermined or required configurations aside from a base architecture networking layout. Install your Linux distribution of choice as you would like, but do pay attention to the cluster's private network configuration. +Warewulf has almost no predetermined or required configurations aside +from a base architecture networking layout. Install your Linux +distribution of choice as you would like, but do pay attention to the +cluster's private network configuration. Network ======= -A clustered resource depends on a private management network. This network can be either persistent (it is always "up" even after provisioning) to temporary which might only be used for provisioning and/or out of band system control and management e.g. IPMI). +A clustered resource depends on a private management network. This +network can be either persistent (it is always "up" even after +provisioning) to temporary which might only be used for provisioning +and/or out of band system control and management e.g. IPMI). -It is important for this management network to be private to the compute resource because Warewulf requires network services on that network which may conflict with services on the production/public network (e.g. DHCP). It is also important from a security perspective as the management network for typical HPC systems have an implied trust level associated with it and generally there is no firewalling or network monitoring occurring on these networks. +It is important for this management network to be private to the +compute resource because Warewulf requires network services on that +network which may conflict with services on the production/public +network (e.g. DHCP). It is also important from a security perspective +as the management network for typical HPC systems have an implied +trust level associated with it and generally there is no firewalling +or network monitoring occurring on these networks. -Usually, the control node is "dual homed" which means it has at least two interface cards, one connected to the private cluster network and one dedicated to the public network (as the figure above demonstrates). +Usually, the control node is "dual homed" which means it has at least +two interface cards, one connected to the private cluster network and +one dedicated to the public network (as the figure above +demonstrates). -> note: It is possible to omit the public network interface with a reverse NAT. Warewulf can operate in this configuration but it extends beyond the scope of this documentation. +.. note:: -Many clusters have more then one private network. This is common for performance critical HPC clusters that implement a high speed and low latency network like InfiniBand. In this case, this network is used for high speed data transfers for inter-process communication between compute nodes and file system IO. + It is possible to omit the public network interface with a reverse + NAT. Warewulf can operate in this configuration but it extends + beyond the scope of this documentation. -Warewulf will need to be configured to use the private cluster management network. Warewulf will use this network for booting the nodes over PXE. There are three network protocols used to accomplish this DHCP/BOOT, TFTP, and HTTP on port ``9873``. Warewulf will use the operating system's provided version of DHCP (ISC-DHCP) and TFTP for the PXE bootstrap to iPXE, and then iPXE will use Warewulf's internal HTTP services to transfer the larger files for provisioning. +Many clusters have more then one private network. This is common for +performance critical HPC clusters that implement a high speed and low +latency network like InfiniBand. In this case, this network is used +for high speed data transfers for inter-process communication between +compute nodes and file system IO. + +Warewulf will need to be configured to use the private cluster +management network. Warewulf will use this network for booting the +nodes over PXE. There are three network protocols used to accomplish +this DHCP/BOOT, TFTP, and HTTP on port ``9873``. Warewulf will use the +operating system's provided version of DHCP (ISC-DHCP) and TFTP for +the PXE bootstrap to iPXE, and then iPXE will use Warewulf's internal +HTTP services to transfer the larger files for provisioning. Addressing ========== -The addressing scheme of your private cluster network is 100% up to the system integrator, but for large clusters, many organizations like to organize the address allocations. Below is a recommended IP addressing scheme which we will use for the rest of this document. +The addressing scheme of your private cluster network is 100% up to +the system integrator, but for large clusters, many organizations like +to organize the address allocations. Below is a recommended IP +addressing scheme which we will use for the rest of this document. * ``10.0.0.1``: Private network address IP * ``255.255.252.0``: Private network subnet mask -Here is an example of how the cluster's address can be divided for a 255 node cluster: +Here is an example of how the cluster's address can be divided for a +255 node cluster: -* ``10.0.0.1 - 10.0.0.255``: Cluster infrastructure including this host, schedulers, file systems, routers, switches, etc. +* ``10.0.0.1 - 10.0.0.255``: Cluster infrastructure including this + host, schedulers, file systems, routers, switches, etc. * ``10.0.1.1 - 10.0.1.255``: DHCP range for booting nodes * ``10.0.2.1 - 10.0.2.255``: Static node addresses -* ``10.0.3.1 - 10.0.3.255``: IPMI and/or out of band addresses for the compute nodes \ No newline at end of file +* ``10.0.3.1 - 10.0.3.255``: IPMI and/or out of band addresses for the + compute nodes diff --git a/userdocs/contents/stateless.rst b/userdocs/contents/stateless.rst index 17b3f4b4..a6e0b84c 100644 --- a/userdocs/contents/stateless.rst +++ b/userdocs/contents/stateless.rst @@ -5,36 +5,82 @@ Stateless Provisioning Why is Provisioning Important ============================= -Clusters are pools of servers bundled together to do a particular job or set of jobs. While there are a number of different use cases for clustering today, Warewulf was originally designed out of necessity. +Clusters are pools of servers bundled together to do a particular job +or set of jobs. While there are a number of different use cases for +clustering today, Warewulf was originally designed out of necessity. -Back in 2000, when Linux clustering was growing up for HPC, the issues of scale became apparent. Of course in HPC, there are many scalability factors which needed to be overcome as we continued to scale up clusters. Pretty early on was the "administrative scaling" which is the factor that full time systems administrators could only maintain so many servers. While homogenous configurations were able to help that, we still had the problem that every installed server became a point of administration, version creep, and debugging. The larger the cluster, the harder this problem was to solve. +Back in 2000, when Linux clustering was growing up for HPC, the issues +of scale became apparent. Of course in HPC, there are many scalability +factors which needed to be overcome as we continued to scale up +clusters. Pretty early on was the "administrative scaling" which is +the factor that full time systems administrators could only maintain +so many servers. While homogenous configurations were able to help +that, we still had the problem that every installed server became a +point of administration, version creep, and debugging. The larger the +cluster, the harder this problem was to solve. Warewulf was created to help with exactly this. Provisioning Overview ===================== -Provisioning in this definition is the process of putting an operating system onto a system. There are many ways to provision operating system images, from copying hard drives, to scripted installs, to automated installs. There are many valuable tools to facilitate this and they all helped to solve this problem. +Provisioning in this definition is the process of putting an operating +system onto a system. There are many ways to provision operating +system images, from copying hard drives, to scripted installs, to +automated installs. There are many valuable tools to facilitate this +and they all helped to solve this problem. -In a cluster environment, this means one could group all of the nodes together, to be installed in bulk. Previous to cluster provisioning system administrators would go around to each cluster node, and install it from scratch, with an ISO or USB thumb drive. This obviously is not scalable. But being able to automatically install hundreds or thousands of computers in parallel and automate the management of these systems completely changed the paradigm. +In a cluster environment, this means one could group all of the nodes +together, to be installed in bulk. Previous to cluster provisioning +system administrators would go around to each cluster node, and +install it from scratch, with an ISO or USB thumb drive. This +obviously is not scalable. But being able to automatically install +hundreds or thousands of computers in parallel and automate the +management of these systems completely changed the paradigm. -There were several cluster provision and management toolkits already available when Warewulf was created and while these tools absolutely helped, there was even more optimization to be had. Stateless computing. +There were several cluster provision and management toolkits already +available when Warewulf was created and while these tools absolutely +helped, there was even more optimization to be had. Stateless +computing. Stateless Provisioning ====================== -The next step past automated installs is to just skip the installation completely; boot directly into the runtime operating system without ever doing an installation. +The next step past automated installs is to just skip the installation +completely; boot directly into the runtime operating system without +ever doing an installation. This is Warewulf. -Stateless provisioning is realizing you never have to install another compute node. Think of it like booting a LiveOS or LiveISO on nodes over the network. This means that no node individually is a point of system administration, but rather the entire cluster, inclusively is administrated as a single unit. +Stateless provisioning is realizing you never have to install another +compute node. Think of it like booting a LiveOS or LiveISO on nodes +over the network. This means that no node individually is a point of +system administration, but rather the entire cluster, inclusively is +administrated as a single unit. -If all cluster nodes are booting the same OS image (or set of OS images), then any individual nodes that have problems is hardware. Debugging software and doing system administration in single points within a cluster is not needed. There is no version drift, because it is not possible for nodes to fall out of sync. Every reboot makes it exactly the same as its neighbors. +If all cluster nodes are booting the same OS image (or set of OS +images), then any individual nodes that have problems is +hardware. Debugging software and doing system administration in single +points within a cluster is not needed. There is no version drift, +because it is not possible for nodes to fall out of sync. Every reboot +makes it exactly the same as its neighbors. -Warewulf provisions the operating system by default to system memory. There is no need for hard drives with Warewulf. +Warewulf provisions the operating system by default to system +memory. There is no need for hard drives with Warewulf. -Previous versions of Warewulf had the ability to write the operating system to hard disk as well as do hybrid provisioning (the core operating system in memory, other pieces overlaid over NFS) but these have been obsoleted in Warewulf v4 as there are easier ways to accomplish the same thing (e.g. use of swap space). +Previous versions of Warewulf had the ability to write the operating +system to hard disk as well as do hybrid provisioning (the core +operating system in memory, other pieces overlaid over NFS) but these +have been obsoleted in Warewulf v4 as there are easier ways to +accomplish the same thing (e.g. use of swap space). -> note: If you wish to provision to the hard drive, we might add that feature back based on user requests but in the mean time, you may wish to look at an automated or scripted installation platform instead of a cluster provisioning system. +.. note:: -In our experience, the Warewulf provisioning model is by far the most advantageous, simplest, and most flexible and scalable cluster provisioning platform available. \ No newline at end of file + If you wish to provision to the hard drive, we might add that + feature back based on user requests but in the mean time, you may + wish to look at an automated or scripted installation platform + instead of a cluster provisioning system. + +In our experience, the Warewulf provisioning model is by far the most +advantageous, simplest, and most flexible and scalable cluster +provisioning platform available. diff --git a/userdocs/contents/templating.rst b/userdocs/contents/templating.rst index e1ca2cd2..692adcbc 100644 --- a/userdocs/contents/templating.rst +++ b/userdocs/contents/templating.rst @@ -2,16 +2,30 @@ Templating ========== -Warewulf uses the ``text/template`` engine to convert dynamic content into static content and auto-populate files with the appropriate data on demand. +Warewulf uses the ``text/template`` engine to convert dynamic content +into static content and auto-populate files with the appropriate data +on demand. -In Warewulf, you can find templates both for the provisioning services (e.g. ``/etc/warewulf/ipxe/``, ``/etc/warewulf/dhcp/``, and ``/etc/warewulf/hosts.tmpl``) as well as within the runtime and system overlays. +In Warewulf, you can find templates both for the provisioning services +(e.g. ``/etc/warewulf/ipxe/``, ``/etc/warewulf/dhcp/``, and +``/etc/warewulf/hosts.tmpl``) as well as within the runtime and system +overlays. (more documentation coming soon) Examples ======== -range +Comment +------- + +.. code-block:: go + + {{ /* This comment won't show up in file, but an empty line /* }} + {{ /* No empty line, line break removed at end of this line /* -}} + {{- /* No empty line, line break removed at front of this line /* }} + +Range ----- iterate over elements of an array @@ -22,7 +36,7 @@ iterate over elements of an array # netdev = {{ $netdev.Hwaddr }} {{ end }} -increment variable in loop +Increment Variable In Loop ^^^^^^^^^^^^^^^^^^^^^^^^^^ iterate over elements of an array and increment ``i`` each loop cycle @@ -35,7 +49,7 @@ iterate over elements of an array and increment ``i`` each loop cycle {{ $i = inc $i }} {{ end }} -decrement +Decrement ^^^^^^^^^ iterate over elements of an array and decrement ``i`` each loop cycle @@ -46,4 +60,154 @@ iterate over elements of an array and decrement ``i`` each loop cycle {{ range $devname, $netdev := .NetDevs }} # netdev{{$i}} = {{ $netdev.Hwaddr }} {{ $i = dec $i }} - {{ end }} \ No newline at end of file + {{ end }} + +Access Tag +---------- + +Acces the value of an individual tag ``foo`` + +.. code-block:: go + + foo: {{ index .Tags "foo" }} + {{ if eq (index .Tags "foo") "baar" -}} + foo: {{ index .Tags "foo" }} + {{ end -}} + +Create Multiple Files +--------------------- + +The following template will create a file called +``ifcfg-NETWORKNAME.xml`` for every network present on the node + +.. code-block:: go + + {{- $host := .BuildHost }} + {{- $time := .BuildTime }} + {{- $source := .BuildSource }} + {{range $devname, $netdev := .NetDevs -}} + {{- $filename := print "ifcfg-" $devname ".xml" }} + {{- file $filename }} + + + {{$netdev.Device}} + {{ if $netdev.Type -}} + {{ $netdev.Type }} + {{ end -}} + + boot + + + + + true + true + + +
+ {{$netdev.IpCIDR}} +
+ {{ if $netdev.Gateway -}} + + + {{$netdev.Gateway}} + + + {{ end -}} +
+ + true + prefer-public + false + + {{ if $netdev.Ipaddr6 -}} + +
+ {{ $netdev.Ipaddr6 }} +
+
+ {{ end -}} +
+ {{ end -}} + +Special Commands +---------------- + +Include +^^^^^^^ + +A file from the host can be include with following template + +.. code-block:: go + + {{ Include file }} + +IncludeFrom +^^^^^^^^^^^ + +With following snippet a file from a given container can be included + +.. code-block:: go + + {{ IncludeFrom container file }} + +IncludeBlock +^^^^^^^^^^^^ + +Includes a file up to the line where a abort string is found. This is +useful, e.g., for the hosts file, which can have local modifications +which are not controlled by warewulf. For this example the abort +string is "# Do not edit after this line" + +.. code-block:: go + + {{ IncludeBlock "/etc/hosts" "# Do not edit after this line" }} + # This block is autogenerated by warewulf + # Host: {{.BuildHost}} + # Time: {{.BuildTime}} + # Source: {{.BuildSource}} + + + # Warewulf Server + {{$.Ipaddr}} warewulf {{$.BuildHost}} + + {{- range $node := $.AllNodes}} {{/* for each node */}} + # Entry for {{$node.Id.Get}} + {{- range $devname, $netdev := $node.NetDevs}} {{/* for each network device on the node */}} + {{- if $netdev.Ipaddr.Defined}} {{/* if we have an ip address on this network device */}} + {{- /* emit the node name as hostname if this is the primary */}} + {{$netdev.Ipaddr.Get}} {{$node.Id.Get}}-{{$devname}} + {{- if $netdev.Device.Defined}} {{$node.Id.Get}}-{{$netdev.Device.Get}}{{end}} + {{- if $netdev.Primary.GetB}} {{$node.Id.Get}}{{end}} + {{- end}} {{/* end if ip */}} + {{- end}} {{/* end for each network device */}} + {{- end}} {{/* end for each node */}} + +Abort +^^^^^ +If ``{{ abort }}`` is found in a template, the resulting file isn't written. + +Nobackup +^^^^^^^^ + +If a file exists on the target, a backup file is written with the +suffix ``.wwbackup``. This only happens for the ``host`` overlay, as +e.g. the ``/etc/hosts`` exists on the host. If this is not the +intended behavior, the ``{{ nobackup }}`` flag can be added to a +template. + +Split +^^^^^ + +A given string can be split into substrings. + +.. code-block:: go + + {{ $x := "a:b:c" -}} + {{ $y := (split $x ":") -}} + {{ range $y }} {{.}} {{ end }} diff --git a/userdocs/contributing/contributing.rst b/userdocs/contributing/contributing.rst index fa9c09ca..a86ab158 100644 --- a/userdocs/contributing/contributing.rst +++ b/userdocs/contributing/contributing.rst @@ -2,35 +2,57 @@ Contributing ============ -Warewulf is an open source project, meaning we have the challenge of limited resources. We are grateful for any support that you can offer. Helping other users, raising issues, helping write documentation, or contributing code are all ways to help! +Warewulf is an open source project, meaning we have the challenge of +limited resources. We are grateful for any support that you can +offer. Helping other users, raising issues, helping write +documentation, or contributing code are all ways to help! Join the community ================== -This is a huge endeavor, and your help would be greatly appreciated! Post to online communities about Warewulf, and request that your distribution vendor, service provider, and system administrators include Warewulf for you! +This is a huge endeavor, and your help would be greatly appreciated! +Post to online communities about Warewulf, and request that your +distribution vendor, service provider, and system administrators +include Warewulf for you! Warewulf on Slack ----------------- -Many of our users come to Slack for quick help with an issue. You can find us at `HPCng `_. +Many of our users come to Slack for quick help with an issue. You can +find us at `HPCng +`_. Raise an Issue ============== -For general bugs/issues, you can open an issue `at the GitHub repo `_. However, if you find a security related issue/problem, please email HPCng directly at `security@hpcng.org `_. More information about the HPCng security policies and procedures can be found `here `_. +For general bugs/issues, you can open an issue `at the GitHub repo +`_. However, if you find +a security related issue/problem, please email HPCng directly at +`security@hpcng.org `_. More information +about the HPCng security policies and procedures can be found `here +`_. Contribute to the code ====================== -We use the traditional `GitHub Flow `_ to develop. This means that you fork the main repo, create a new branch to make changes, and submit a pull request (PR) to the master branch. +We use the traditional `GitHub Flow +`_ to develop. This means +that you fork the main repo, create a new branch to make changes, and +submit a pull request (PR) to the master branch. -Check out our official `CONTRIBUTING.md `_ document, which also includes a `code of conduct `_. +Check out our official `CONTRIBUTING.md +`_ +document, which also includes a `code of conduct +`_. Step 1. Fork the repo --------------------- -To contribute to Warewulf, you should obtain a GitHub account and fork the `Warewulf `_ repository. Once forked, clone your fork of the repo to your computer. (Obviously, you should replace ``your-username`` with your GitHub username.) +To contribute to Warewulf, you should obtain a GitHub account and fork +the `Warewulf `_ repository. Once +forked, clone your fork of the repo to your computer. (Obviously, you +should replace ``your-username`` with your GitHub username.) .. code-block:: bash @@ -40,10 +62,11 @@ To contribute to Warewulf, you should obtain a GitHub account and fork the `Ware Step 2. Checkout a new branch ----------------------------- -`Branches `_` are a way of -isolating your features from the main branch. Given that we’ve just cloned the -repo, we will probably want to make a new branch from master in which to work on -our new feature. Lets call that branch ``new-feature``: +`Branches `_ are a way +of isolating your features from the main branch. Given that we’ve just +cloned the repo, we will probably want to make a new branch from +master in which to work on our new feature. Lets call that branch +``new-feature``: .. code-block:: bash @@ -51,37 +74,50 @@ our new feature. Lets call that branch ``new-feature``: git checkout -b new-feature .. note:: - You can always check which branch you are in by running ``git branch``. + + You can always check which branch you are in by running ``git + branch``. Step 3. Make your changes ------------------------- -On your new branch, go nuts! Make changes, test them, and when you are happy commit the changes to the branch: +On your new branch, go nuts! Make changes, test them, and when you are +happy commit the changes to the branch: .. code-block:: bash git add file-changed1 file-changed2... git commit -m "what changed?" -This commit message is important - it should describe exactly the changes that you have made. Good commit messages read like so: +This commit message is important - it should describe exactly the +changes that you have made. Good commit messages read like so: .. code-block:: bash git commit -m "changed function getConfig in functions.go to output csv to fix #2" git commit -m "updated docs about shell to close #10" -The tags ``close #10`` and ``fix #2`` are referencing issues that are posted on the upstream repo where you will direct your pull request. When your PR is merged into the master branch, these messages will automatically close the issues, and further, they will link your commits directly to the issues they intend to fix. This will help future maintainers understand your contribution, or (hopefully not) revert the code back to a previous version if necessary. +The tags ``close #10`` and ``fix #2`` are referencing issues that are +posted on the upstream repo where you will direct your pull +request. When your PR is merged into the master branch, these messages +will automatically close the issues, and further, they will link your +commits directly to the issues they intend to fix. This will help +future maintainers understand your contribution, or (hopefully not) +revert the code back to a previous version if necessary. Step 4. Push your branch to your fork ------------------------------------- -When you are done with your commits, you should push your branch to your fork (and you can also continuously push commits here as you work): +When you are done with your commits, you should push your branch to +your fork (and you can also continuously push commits here as you +work): .. code-block:: bash git push origin new-feature -Note that you should always check the status of your branches to see what has been pushed (or not): +Note that you should always check the status of your branches to see +what has been pushed (or not): .. code-block:: bash @@ -90,12 +126,24 @@ Note that you should always check the status of your branches to see what has be Step 5. Submit a Pull Request ----------------------------- -Once you have pushed your branch, then you can go to your fork (in the web GUI on GitHub) and `submit a Pull Request `_. Regardless of the name of your branch, your PR should be submitted to the ``main`` branch. Submitting your PR will open a conversation thread for the maintainers of Warewulf to discuss your contribution. At this time, the continuous integration that is linked with the code base will also be executed. If there is an issue, or if the maintainers suggest changes, you can continue to push commits to your branch and they will update the Pull Request. +Once you have pushed your branch, then you can go to your fork (in the +web GUI on GitHub) and `submit a Pull Request +`_. Regardless +of the name of your branch, your PR should be submitted to the +``main`` branch. Submitting your PR will open a conversation thread +for the maintainers of Warewulf to discuss your contribution. At this +time, the continuous integration that is linked with the code base +will also be executed. If there is an issue, or if the maintainers +suggest changes, you can continue to push commits to your branch and +they will update the Pull Request. Step 6. Keep your branch in sync -------------------------------- -Cloning the repo will create an exact copy of the Warewulf repository at that moment. As you work, your branch may become out of date as others merge changesinto the upstream master. In the event that you need to update a branch, you will need to follow the next steps: +Cloning the repo will create an exact copy of the Warewulf repository +at that moment. As you work, your branch may become out of date as +others merge changesinto the upstream master. In the event that you +need to update a branch, you will need to follow the next steps: .. code-block:: bash @@ -107,4 +155,4 @@ Cloning the repo will create an exact copy of the Warewulf repository at that mo # to update your fork git push origin master git checkout new-feature - git merge master \ No newline at end of file + git merge master diff --git a/userdocs/contributing/development-environment-kvm.rst b/userdocs/contributing/development-environment-kvm.rst index 3c9111a5..023b3eb9 100644 --- a/userdocs/contributing/development-environment-kvm.rst +++ b/userdocs/contributing/development-environment-kvm.rst @@ -56,7 +56,7 @@ Turn off default network dhcp on server master1 sudo virsh net-start default -Build and install warewulf on wwdev +Build and install Warewulf on wwdev =================================== .. code-block:: bash @@ -125,4 +125,4 @@ Build and install warewulf on wwdev sudo wwctl server start sudo wwctl server status -Boot your node and watch the bash and the output of the Warewulfd process \ No newline at end of file +Boot your node and watch the bash and the output of the Warewulfd process diff --git a/userdocs/contributing/development-environment-vbox.rst b/userdocs/contributing/development-environment-vbox.rst index f75fb756..8d504aa5 100644 --- a/userdocs/contributing/development-environment-vbox.rst +++ b/userdocs/contributing/development-environment-vbox.rst @@ -1,11 +1,12 @@ - ==================================== Development Environment (VirtualBox) ==================================== I have VirtualBox running on my desktop. -1. Create a NAT Network (a private vlan) to be used for the Warewlf Server and compute nodes inside the VirtualBox. Make sure to turnoff DHCP service within this NAT Network. +1. Create a NAT Network (a private vlan) to be used for the Warewlf + Server and compute nodes inside the VirtualBox. Make sure to + turnoff DHCP service within this NAT Network. .. code-block:: console @@ -13,7 +14,12 @@ I have VirtualBox running on my desktop. VBoxManage natnetwork add --netname wwnatnetwork --network "10.0.8.0/24" --enable --dhcp off -2. Create a Centos 7 development Virtual machine (wwdev) to be used as the Warewulf Server. Enable two Network adapters one with a standard NAT and SSH port mapping such that you can access this VM from the host machine. Assign the second network adapter to the NAT Network created in step #1. Assign sufficient memory (e.g: 4GB) to the VM. +2. Create a Centos 7 development Virtual machine (wwdev) to be used as + the Warewulf Server. Enable two Network adapters one with a + standard NAT and SSH port mapping such that you can access this VM + from the host machine. Assign the second network adapter to the NAT + Network created in step #1. Assign sufficient memory (e.g: 4GB) to + the VM. .. code-block:: console @@ -28,7 +34,7 @@ I have VirtualBox running on my desktop. # Next attach the second Network adapter #2 to the NAT Network and you should be able to choose # the 'wwnatnetwork' created above in step #1 from the drop down list. -3. Build and install warewulf on wwdev +3. Build and install Warewulf on wwdev .. code-block:: console @@ -135,7 +141,14 @@ I have VirtualBox running on my desktop. sudo wwctl server start sudo wwctl server status -4. Create a new guest VM instance inside the VirtualBox to be the warewulf client/compute node. Under the system configuration make sure to select the optical and network options only for the boot order. The default iPXE used by VirtualBox does not come with bzImage capability which is needed for warewulf. Download the ipxe.iso available at ipxe.org and mount the ipxe.iso to the optical drive. Enable one Network adapter for this VM and assign it to the NAT Network created in step #1 above. +4. Create a new guest VM instance inside the VirtualBox to be the + Warewulf client/compute node. Under the system configuration make + sure to select the optical and network options only for the boot + order. The default iPXE used by VirtualBox does not come with + bzImage capability which is needed for Warewulf. Download the + ipxe.iso available at ipxe.org and mount the ipxe.iso to the + optical drive. Enable one Network adapter for this VM and assign it + to the NAT Network created in step #1 above. .. code-block:: console @@ -144,4 +157,5 @@ I have VirtualBox running on my desktop. # VM Settings -> Storage and mount the above download ipxe.so to the Optical Drive. # VM Settings -> Network Enable adapter #1, attach to 'Nat Network' and choose 'wwnatnetwork' from the drop down list. -Boot your node and watch the console and the output of the Warewulfd process \ No newline at end of file +Boot your node and watch the console and the output of the Warewulfd +process. diff --git a/userdocs/contributing/documentation.rst b/userdocs/contributing/documentation.rst index be6b8aee..e65225b0 100644 --- a/userdocs/contributing/documentation.rst +++ b/userdocs/contributing/documentation.rst @@ -2,10 +2,21 @@ Documentation ============= -We (like almost all open source software providers) have a documentation dilemma… We tend to focus on the code features and functionality before working on documentation. And there is very good reason for this: we want to share the love so nobody feels left out! +We (like almost all open source software providers) have a +documentation dilemma… We tend to focus on the code features and +functionality before working on documentation. And there is very good +reason for this: we want to share the love so nobody feels left out! -You can contribute to the documentation by `raising an issue to suggest an improvement `_ or by sending a `pull request `_ on `our repository `_. +You can contribute to the documentation by `raising an issue to +suggest an improvement +`_ or by sending a +`pull request `_ on +`our repository `_. -The current documentation is generated with `Docusaurus `_. +The current documentation is generated with `Docusaurus +`_. -For more information on using Git and GitHub to create a pull request suggesting additions and edits to the docs, see the `section on contributing to the code `_. The procedure is identical for contributions to the documentation and the code base. \ No newline at end of file +For more information on using Git and GitHub to create a pull request +suggesting additions and edits to the docs, see the `section on +contributing to the code `_. The procedure is identical +for contributions to the documentation and the code base. diff --git a/userdocs/debian/debian.md b/userdocs/debian/debian.md index 6bf5622d..0afec37b 100644 --- a/userdocs/debian/debian.md +++ b/userdocs/debian/debian.md @@ -12,7 +12,7 @@ docker build -t docker.psi.ch:5000/debian:buster-slim . docker push docker.psi.ch:5000/debian:buster-slim ``` -### import docker container into warewulf +### import docker container into Warewulf ```bash wwctl container import docker://docker.psi.ch:5000/debian:buster-slim debian-10:slim diff --git a/userdocs/index.rst b/userdocs/index.rst index 17c4314a..7b2b2ecc 100644 --- a/userdocs/index.rst +++ b/userdocs/index.rst @@ -46,4 +46,4 @@ Welcome to the Warewulf User Guide! :maxdepth: 2 :caption: Reference - Glossary \ No newline at end of file + Glossary diff --git a/userdocs/quickstart/el7.rst b/userdocs/quickstart/el7.rst index cc70c5d2..a82445cd 100644 --- a/userdocs/quickstart/el7.rst +++ b/userdocs/quickstart/el7.rst @@ -17,7 +17,8 @@ Install Warewulf and dependencies Configure firewalld =================== -Restart firewalld to register the added service file, add the service to the default zone, and reload. +Restart firewalld to register the added service file, add the service +to the default zone, and reload. .. code-block:: bash @@ -30,9 +31,10 @@ Restart firewalld to register the added service file, add the service to the def Configure the controller ======================== -Edit the file ``/etc/warewulf/warewulf.conf`` and ensure that you've set the appropriate -configuration parameters. Here are some of the defaults for reference assuming that ``192.168.200.1`` -is the IP address of your cluster's private network interface: +Edit the file ``/etc/warewulf/warewulf.conf`` and ensure that you've +set the appropriate configuration parameters. Here are some of the +defaults for reference assuming that ``192.168.200.1`` is the IP +address of your cluster's private network interface: .. code-block:: yaml @@ -59,8 +61,10 @@ is the IP address of your cluster's private network interface: - /var/warewulf .. note:: - The DHCP range ends at `192.168.200.99` and as you will see below, the first node static IP - address (post boot) is configured to `192.168.200.100`. + + The DHCP range ends at ``192.168.200.99`` and as you will see + below, the first node static IP address (post boot) is configured + to ``192.168.200.100``. Start and enable the Warewulf service ===================================== @@ -73,21 +77,29 @@ Start and enable the Warewulf service Configure system services automatically ======================================= -There are a number of services and configurations that Warewulf relies on to operate. -If you wish to configure all services, you can do so individually (omitting the ``--all``) -will print a help and usage instructions. +There are a number of services and configurations that Warewulf relies +on to operate. If you wish to configure all services, you can do so +individually (omitting the ``--all``) will print a help and usage +instructions. .. code-block:: bash sudo wwctl configure --all .. note:: - If you just installed the system fresh and have SELinux enforcing, you may need to reboot the system at this stage to properly set the contexts of the TFTP contents. After rebooting, you might also need to run ``$ sudo restorecon -Rv /var/lib/tftpboot/`` if there are errors with TFTP still. + + If you just installed the system fresh and have SELinux enforcing, + you may need to reboot the system at this stage to properly set the + contexts of the TFTP contents. After rebooting, you might also need + to run ``restorecon -Rv /var/lib/tftpboot/`` if there are + errors with TFTP still. Pull and build the VNFS container and kernel ============================================ -This will pull a basic VNFS container from Docker Hub and import the default running kernel from the controller node and set both in the "default" node profile. +This will pull a basic VNFS container from Docker Hub and import the +default running kernel from the controller node and set both in the +"default" node profile. .. code-block:: bash @@ -97,13 +109,19 @@ This will pull a basic VNFS container from Docker Hub and import the default run Set up the default node profile =============================== -The ``--setdefault`` arguments above will automatically set those entries in the default profile, but if you wanted to set them by hand to something different, you can do the following: +The ``--setdefault`` arguments above will automatically set those +entries in the default profile, but if you wanted to set them by hand +to something different, you can do the following: .. code-block:: bash sudo wwctl profile set -y default -K $(uname -r) -C centos-7 -Next we set some default networking configurations for the first ethernet device. On modern Linux distributions, the name of the device is not critical, as it will be setup according to the HW address. Because all nodes will share the netmask and gateway configuration, we can set them in the default profile as follows: +Next we set some default networking configurations for the first +ethernet device. On modern Linux distributions, the name of the device +is not critical, as it will be setup according to the HW +address. Because all nodes will share the netmask and gateway +configuration, we can set them in the default profile as follows: .. code-block:: bash @@ -113,15 +131,21 @@ Next we set some default networking configurations for the first ethernet device Add a node ========== -Adding nodes can be done while setting configurations in one command. Here we are setting the IP address of ``eth0`` and setting this node to be discoverable, which will then automatically have the HW address added to the configuration as the node boots. +Adding nodes can be done while setting configurations in one +command. Here we are setting the IP address of ``eth0`` and setting +this node to be discoverable, which will then automatically have the +HW address added to the configuration as the node boots. -Node names must be unique. If you have node groups and/or multiple clusters, designate them using dot notation. +Node names must be unique. If you have node groups and/or multiple +clusters, designate them using dot notation. -Note that the full node configuration comes from both cascading profiles and node configurations which always supersede profile configurations. +Note that the full node configuration comes from both cascading +profiles and node configurations which always supersede profile +configurations. .. code-block:: bash sudo wwctl node add n0000.cluster --netname default -I 192.168.200.100 --discoverable sudo wwctl node list -a n0000 -Turn on your compute node and watch it boot! \ No newline at end of file +Turn on your compute node and watch it boot! diff --git a/userdocs/quickstart/el8.rst b/userdocs/quickstart/el8.rst index 7d6cf466..215b0037 100644 --- a/userdocs/quickstart/el8.rst +++ b/userdocs/quickstart/el8.rst @@ -19,7 +19,8 @@ Install Warewulf and dependencies Configure firewalld =================== -Restart firewalld to register the added service file, add the service to the default zone, and reload. +Restart firewalld to register the added service file, add the service +to the default zone, and reload. .. code-block:: bash @@ -32,9 +33,10 @@ Restart firewalld to register the added service file, add the service to the def Configure the controller ======================== -Edit the file ``/etc/warewulf/warewulf.conf`` and ensure that you've set the appropriate -configuration parameters. Here are some of the defaults for reference assuming that ``192.168.200.1`` -is the IP address of your cluster's private network interface: +Edit the file ``/etc/warewulf/warewulf.conf`` and ensure that you've +set the appropriate configuration parameters. Here are some of the +defaults for reference assuming that ``192.168.200.1`` is the IP +address of your cluster's private network interface: .. code-block:: yaml @@ -61,8 +63,10 @@ is the IP address of your cluster's private network interface: - /var/warewulf .. note:: -The DHCP range ends at ``192.168.200.99`` and as you will see below, the first node static IP -address (post boot) is configured to ``192.168.200.100``. + + The DHCP range ends at ``192.168.200.99`` and as you will see + below, the first node static IP address (post boot) is configured + to ``192.168.200.100``. Start and enable the Warewulf service ===================================== @@ -75,22 +79,29 @@ Start and enable the Warewulf service Configure system services automatically ======================================= -There are a number of services and configurations that Warewulf relies on to operate. -If you wish to configure all services, you can do so individually (omitting the ``--all``) -will print a help and usage instructions. +There are a number of services and configurations that Warewulf relies +on to operate. If you wish to configure all services, you can do so +individually (omitting the ``--all``) will print a help and usage +instructions. .. code-block:: bash sudo wwctl configure --all .. note:: - If you just installed the system fresh and have SELinux enforcing, you may need to reboot the system at this stage to properly set the contexts of the TFTP contents. After rebooting, you might also need to run ``$ sudo restorecon -Rv /var/lib/tftpboot/`` if there are errors with TFTP still. + + If you just installed the system fresh and have SELinux enforcing, + you may need to reboot the system at this stage to properly set the + contexts of the TFTP contents. After rebooting, you might also need + to run ``$ sudo restorecon -Rv /var/lib/tftpboot/`` if there are + errors with TFTP still. Pull and build the VNFS container (including the kernel) ======================================================== -This will pull a basic VNFS container from Docker Hub and import the default running -kernel from the controller node and set both in the "default" node profile. +This will pull a basic VNFS container from Docker Hub and import the +default running kernel from the controller node and set both in the +"default" node profile. .. code-block:: bash @@ -100,26 +111,29 @@ kernel from the controller node and set both in the "default" node profile. Set up the default node profile =============================== -Node configurations can be set via node profiles. Each node by default is configured to -be part of the ``default`` node profile, so any changes you make to that profile will -affect all nodes. +Node configurations can be set via node profiles. Each node by default +is configured to be part of the ``default`` node profile, so any +changes you make to that profile will affect all nodes. -The following command will set the container we just imported above to the ``default`` node profile: +The following command will set the container we just imported above to +the ``default`` node profile: .. code-block:: bash sudo wwctl profile set --yes --container rocky-8 "default" -Next we set some default networking configurations for the first ethernet device. On -modern Linux distributions, the name of the device is not critical, as it will be setup -according to the HW address. Because all nodes will share the netmask and gateway +Next we set some default networking configurations for the first +ethernet device. On modern Linux distributions, the name of the device +is not critical, as it will be setup according to the HW +address. Because all nodes will share the netmask and gateway configuration, we can set them in the default profile as follows: .. code-block:: bash sudo wwctl profile set --yes --netdev eth0 --netmask 255.255.255.0 --gateway 192.168.200.1 "default" -Once those configurations have been set, you can view the changes by listing the profiles as follows: +Once those configurations have been set, you can view the changes by +listing the profiles as follows: .. code-block:: bash @@ -128,24 +142,27 @@ Once those configurations have been set, you can view the changes by listing the Add a node ========== -Adding nodes can be done while setting configurations in one command. Here we are setting -the IP address of ``eth0`` and setting this node to be discoverable, which will then -automatically have the HW address added to the configuration as the node boots. +Adding nodes can be done while setting configurations in one +command. Here we are setting the IP address of ``eth0`` and setting +this node to be discoverable, which will then automatically have the +HW address added to the configuration as the node boots. -Node names must be unique. If you have node groups and/or multiple clusters, designate -them using dot notation. +Node names must be unique. If you have node groups and/or multiple +clusters, designate them using dot notation. -Note that the full node configuration comes from both cascading profiles and node -configurations which always supersede profile configurations. +Note that the full node configuration comes from both cascading +profiles and node configurations which always supersede profile +configurations. .. code-block:: bash sudo wwctl node add n0000.cluster --ipaddr 192.168.200.100 --discoverable -At this point you can view the basic configuration of this node by typing the following: +At this point you can view the basic configuration of this node by +typing the following: .. code-block:: bash sudo wwctl node list -a n0000.cluster -Turn on your compute node and watch it boot! \ No newline at end of file +Turn on your compute node and watch it boot! diff --git a/userdocs/quickstart/suse15.rst b/userdocs/quickstart/suse15.rst index 8f94db08..7c0c58e2 100644 --- a/userdocs/quickstart/suse15.rst +++ b/userdocs/quickstart/suse15.rst @@ -20,7 +20,8 @@ Install Warewulf and dependencies make all sudo make install -The standard configuration template for the dhcpd service is installed at the wrong location, you have to fix this with +The standard configuration template for the dhcpd service is installed +at the wrong location, you have to fix this with .. code-block:: bash @@ -29,16 +30,19 @@ The standard configuration template for the dhcpd service is installed at the wr Install Warewulf from the open build service ============================================ -You can also just install the 'warewulf4' package with ``zypper`` from the openbuild service. Up to date versions are available on the devel project +You can also just install the 'warewulf4' package with ``zypper`` from +the openbuild service. Up to date versions are available on the devel +project ``https://build.opensuse.org/project/show/network:cluster`` Configure the controller ======================== -Edit the file ``/etc/warewulf/warewulf.conf`` and ensure that you've set the appropriate -configuration paramaters. Here are some of the defaults for reference assuming that ``192.168.200.1`` -is the IP address of your cluster's private network interface: +Edit the file ``/etc/warewulf/warewulf.conf`` and ensure that you've +set the appropriate configuration paramaters. Here are some of the +defaults for reference assuming that ``192.168.200.1`` is the IP +address of your cluster's private network interface: .. code-block:: yaml @@ -74,8 +78,10 @@ is the IP address of your cluster's private network interface: systemd name: nfs-server .. note:: - The DHCP range ends at ``192.168.200.99`` and as you will see below, the first node static IP - address (post boot) is configured to ``192.168.200.100``. + + The DHCP range ends at ``192.168.200.99`` and as you will see + below, the first node static IP address (post boot) is configured + to ``192.168.200.100``. Start and enable the Warewulf service ===================================== @@ -88,13 +94,16 @@ Start and enable the Warewulf service Configure system services automatically ======================================= -There are a number of services and configurations that Warewulf relies on to operate. -If you wish to configure all services, you can do so individually (omitting the ``--all``) -will print a help and usage instructions. +There are a number of services and configurations that Warewulf relies +on to operate. If you wish to configure all services, you can do so +individually (omitting the ``--all``) will print a help and usage +instructions. .. note:: - If the ``dhcpd`` service was not used before you will have to add the interface on which - the cluster network is running to the ``DHCP_INTERFACE`` in the file ``/etc/sysconfig/dhcpd``. + + If the ``dhcpd`` service was not used before you will have to add + the interface on which the cluster network is running to the + ``DHCP_INTERFACE`` in the file ``/etc/sysconfig/dhcpd``. .. code-block:: bash @@ -103,8 +112,9 @@ will print a help and usage instructions. Pull and build the VNFS container and kernel ============================================ -This will pull a basic VNFS container from Docker Hub and import the default running -kernel from the controller node and set both in the "default" node profile. +This will pull a basic VNFS container from Docker Hub and import the +default running kernel from the controller node and set both in the +"default" node profile. .. code-block:: bash @@ -113,17 +123,18 @@ kernel from the controller node and set both in the "default" node profile. Set up the default node profile =============================== -The ``--setdefault`` arguments above will automatically set those entries in the default -profile, but if you wanted to set them by hand to something different, you can do the -following: +The ``--setdefault`` arguments above will automatically set those +entries in the default profile, but if you wanted to set them by hand +to something different, you can do the following: .. code-block:: bash sudo wwctl profile set -y -C leap15.4 -Next we set some default networking configurations for the first ethernet device. On -modern Linux distributions, the name of the device is not critical, as it will be setup -according to the HW address. Because all nodes will share the netmask and gateway +Next we set some default networking configurations for the first +ethernet device. On modern Linux distributions, the name of the device +is not critical, as it will be setup according to the HW +address. Because all nodes will share the netmask and gateway configuration, we can set them in the default profile as follows: .. code-block:: bash @@ -134,15 +145,17 @@ configuration, we can set them in the default profile as follows: Add a node ========== -Adding nodes can be done while setting configurations in one command. Here we are setting -the IP address of ``eth0`` and setting this node to be discoverable, which will then -automatically have the HW address added to the configuration as the node boots. +Adding nodes can be done while setting configurations in one +command. Here we are setting the IP address of ``eth0`` and setting +this node to be discoverable, which will then automatically have the +HW address added to the configuration as the node boots. -Node names must be unique. If you have node groups and/or multiple clusters, designate -them using dot notation. +Node names must be unique. If you have node groups and/or multiple +clusters, designate them using dot notation. -Note that the full node configuration comes from both cascading profiles and node -configurations which always supersede profile configurations. +Note that the full node configuration comes from both cascading +profiles and node configurations which always supersede profile +configurations. .. code-block:: bash @@ -154,22 +167,25 @@ Warewulf Overlays There are two types of overlays: system and runtime overlays. -System overlays are provisioned to the node before ``/sbin/init`` is called. This enables us -to prepopulate node configurations with content that is node specific like networking and -service configurations. +System overlays are provisioned to the node before ``/sbin/init`` is +called. This enables us to prepopulate node configurations with +content that is node specific like networking and service +configurations. -Runtime overlays are provisioned after the node has booted and periodically during the -normal runtime of the node. Because these overlays are provisioned at periodic intervals, -they are very useful for content that changes, like users and groups. +Runtime overlays are provisioned after the node has booted and +periodically during the normal runtime of the node. Because these +overlays are provisioned at periodic intervals, they are very useful +for content that changes, like users and groups. -Overlays are generated from a template structure that is viewed using the ``wwctl overlay`` -commands. Files that end in the ``.ww`` suffix are templates and abide by standard -text/template rules. This supports loops, arrays, variables, and functions making overlays -extremely flexible. +Overlays are generated from a template structure that is viewed using +the ``wwctl overlay`` commands. Files that end in the ``.ww`` suffix +are templates and abide by standard text/template rules. This supports +loops, arrays, variables, and functions making overlays extremely +flexible. - -All overlays are compiled before being provisioned. This accelerates the provisioning -process because there is less to do when nodes are being managed at scale. +All overlays are compiled before being provisioned. This accelerates +the provisioning process because there is less to do when nodes are +being managed at scale. Here are some of the common ``overlay`` commands: @@ -180,4 +196,4 @@ Here are some of the common ``overlay`` commands: sudo wwctl overlay edit default /etc/hello_world.ww sudo wwctl overlay build -a -Boot your compute node and watch it boot! \ No newline at end of file +Boot your compute node and watch it boot! diff --git a/userdocs/reference/glossary.rst b/userdocs/reference/glossary.rst index c68a27ad..2f576530 100644 --- a/userdocs/reference/glossary.rst +++ b/userdocs/reference/glossary.rst @@ -3,10 +3,19 @@ Glossary ======== Container - Containers are used by Warewulf as the template for the VNFS image. Warewulf containers can be any type of OCI or Singularity standard image formats but maintained on disk as an "OCI bundle". Warewulf integrates with Docker, Docker Hub, any OCI registery, Singularity, standard chroots, etc. + Containers are used by Warewulf as the template for the VNFS + image. Warewulf containers can be any type of OCI or Singularity + standard image formats but maintained on disk as an "OCI + bundle". Warewulf integrates with Docker, Docker Hub, any OCI + registery, Singularity, standard chroots, etc. Controller - The controller node(s) are the resources responsible for management, control, and administration of the cluster. Historically these systems have been called "master", "head", or "administrative" nodes, but we feel the term "controller" is more appropriate and descriptive of the role of this system. + The controller node(s) are the resources responsible for + management, control, and administration of the + cluster. Historically these systems have been called "master", + "head", or "administrative" nodes, but we feel the term + "controller" is more appropriate and descriptive of the role of + this system. Initramfs @@ -17,4 +26,7 @@ Overlays Virtual Node File System (VNFS) Workers - Worker nodes are the systems that are being provisioned by Warewulf. The roles of these systems could be "compute", "storage", "GPU", "IO", etc. which would typically be used as a prefix, for example: "**compute worker node**" \ No newline at end of file + Worker nodes are the systems that are being provisioned by + Warewulf. The roles of these systems could be "compute", + "storage", "GPU", "IO", etc. which would typically be used as a + prefix, for example: "**compute worker node**"