Code Guidelines
Warewulf is written in Go. This page describes how the source is laid out and the conventions that keep it consistent. Read it before changing Go code, and follow the existing precedent where this page is silent.
Source Layout
cmd/:mainpackages forwwctlandwwclient. They only call intointernal/app/.internal/app/wwctl/: one package perwwctlsubcommand, nested like the command line (e.g.internal/app/wwctl/node/set/).internal/app/wwclient/: the node-side client.internal/pkg/: shared packages. The ones most changes touch:node: thenodes.confdata model (nodes and profiles).config: thewarewulf.confdata model.overlay: overlay building, template rendering, and the template function map.image: node image import and build.warewulfd: the provisioning daemon and its REST API.wwlog: logging.util: general helpers (files, cpio, YAML,BoolP).hostlist: node-range expansion (n[01-10]).testenv: the test fixture (see Writing Tests).
overlays/<name>/rootfs/: the distribution overlays; their tests are inoverlays/<name>/internal/.etc/,lib/,include/,dracut/: default configuration, BMC templates, systemd and other installed files, and thewwinitdracut module.
wwctl Subcommands
Each subcommand package has three files:
root.go:GetCommand()builds thecobra.Command, registers its flags and completions, and setsRunE: CobraRunE(&vars).main.go:CobraRunEdoes the work.main_test.go: tests that runGetCommand()with arguments.
Use the shared helpers in internal/app/wwctl/completions and
internal/app/wwctl/flags for completions and common flags. Return errors
from RunE, wrapped with context (fmt.Errorf("...: %w", err)), rather
than exiting.
Node and Profile Fields
nodes.conf fields are struct fields in internal/pkg/node. Their struct
tags drive the rest: yaml and json name the field, lopt and
sopt name the wwctl long and short flags, and comment is the help
text. wwctl node set and wwctl profile set generate their flags from
these tags, so a new field needs only the tags, not hand-written flag code.
Document a new field in the user guide and in docs/man/man5/nodes.conf.5.
warewulf.conf fields are in internal/pkg/config, with defaults set by
default struct tags. Document a new field in
userdocs/server/configuration.rst and docs/man/man5/warewulf.conf.5.
If a change alters the on-disk format of either file, check whether
internal/pkg/upgrade (wwctl upgrade) needs a matching step so existing
configurations still read.
Logging and Output
Use internal/pkg/wwlog rather than fmt or log. Choose the level by
audience: Info and Output are what the user asked for and go to
stdout; Verbose (wwctl -v) and Debug (wwctl -d) are detail;
Warn and Error report problems. Everything except Info and
Output goes to stderr. The *Exc variants take an error.
Existing Helpers
Before writing a helper, check internal/pkg/util and the standard library
(slices, maps, path/filepath). Expand node arguments with
hostlist.Expand. When behavior depends on an external tool that differs
between distributions (cpio, dracut), detect the feature rather than
comparing versions; util.CpioCreate is an example.
A new overlay template function goes in buildTemplateFuncMap in
internal/pkg/overlay/overlay.go, with a section in
Templates and a case in
internal/pkg/overlay/overlay_test.go.
Generated Files
Some files are generated. Edit their sources and let make regenerate them:
internal/pkg/config/buildconfig.go,warewulf.specandinclude/systemd/warewulfd.servicecome from their.infiles (make config).userdocs/reference/anddocs/man/man1/come fromwwctlitself (make docs).LICENSE_DEPENDENCIES.mdcomes fromscripts/update-license-dependencies.sh; CI checks that it is current.
make targets that need the generated files create them. Run make config
first if you call go directly.
Checks
CI (.github/workflows/testsuite.yml) runs the same targets you can run
locally:
make fmt
make lint vet staticcheck deadcode shellcheck
make test
deadcode fails on any unused function, so remove helpers you stop using.
lint, deadcode and shellcheck download their tools into .tools/
on first use, and builds vendor modules first; set OFFLINE_BUILD=1 to skip
both when there is no network access.