d/libn/i/nftables: extract templates into files

Errors when parsing and applying templates reference the offending line
number and column within the template text. It's not so easy to
correlate the position mentioned in the error with the template source
when the template is inlined as string literals in a Go source file.
Extract the nftables reload and incremental-update templates to
dedicated files and embed them into the program with //go:embed
directives so the offset mentioned in error messages is the offset
within the template file.

Having the templates in dedicated files also enables more rich IDE
integration. gopls for instance will highlight template directives (when
the `semanticTokens` setting is enabled) and report template syntax
errors inline when a template is in a dedicated file.

Signed-off-by: Cory Snider <csnider@mirantis.com>
This commit is contained in:
Cory Snider
2026-06-25 12:49:58 -04:00
parent b086f5e226
commit 5a86f78318
3 changed files with 130 additions and 104 deletions

View File

@@ -0,0 +1,60 @@
{{- /*
This template is used with text/template to generate an nftables command file
(which will be applied atomically). Updates using this template are always
incremental.
Steps are:
- declare the table and its sets/maps with empty versions of modified chains, so that
they can be flushed/deleted if they don't yet exist. (They need to be flushed in case
a version of them was left behind by an old incarnation of the daemon. But, it's an
error to flush or delete something that doesn't exist. So, avoid having to parse nft's
stderr to work out what happened by making sure they do exist before flushing.)
- if the table is newly declared, flush rules from its chains
- flush each newly declared map/set
- delete deleted map/set elements
- flush modified chains
- delete deleted chains
- re-populate modified chains
- add new map/set elements
*/ -}}
{{$family := .Family}}{{$tableName := .Name}}
table {{$family}} {{$tableName}} {
{{range .Maps}}map {{.Name}} {
{{.ElementTypeExpr}}
{{if len .Flags}}flags{{range .Flags}} {{.}}{{end}}{{end}}
}
{{end}}
{{range .Sets}}set {{.Name}} {
{{.ElementTypeExpr}}
{{if len .Flags}}flags{{range .Flags}} {{.}}{{end}}{{end}}
}
{{end}}
{{range .Chains}}{{if .MustFlush}}chain {{.Name}} {
{{if .ChainType}}type {{.ChainType}} hook {{.Hook}} priority {{.Priority}}; policy {{.Policy}}{{end}}
} ; {{end}}{{end}}
}
{{if .MustFlush}}flush table {{$family}} {{$tableName}}{{end}}
{{range .Maps}}{{if .MustFlush}}flush map {{$family}} {{$tableName}} {{.Name}}
{{end}}{{end}}
{{range .Sets}}{{if .MustFlush}}flush set {{$family}} {{$tableName}} {{.Name}}
{{end}}{{end}}
{{range .Chains}}{{if .MustFlush}}flush chain {{$family}} {{$tableName}} {{.Name}}
{{end}}{{end}}
{{range .Maps}}{{if .DeletedElements}}delete element {{$family}} {{$tableName}} {{.Name}} { {{range $k,$v := .DeletedElements}}{{$k}}, {{end}} }
{{end}}{{end}}
{{range .Sets}}{{if .DeletedElements}}delete element {{$family}} {{$tableName}} {{.Name}} { {{range $k,$v := .DeletedElements}}{{$k}}, {{end}} }
{{end}}{{end}}
{{range .DeleteCommands}}{{.}}
{{end}}
table {{$family}} {{$tableName}} {
{{range .Chains}}{{if .MustFlush}}chain {{.Name}} {
{{if .ChainType}}type {{.ChainType}} hook {{.Hook}} priority {{.Priority}}; policy {{.Policy}}{{end}}
{{range .Rules}}{{.}}
{{end}}
}
{{end}}{{end}}
}
{{range .Maps}}{{if .AddedElements}}add element {{$family}} {{$tableName}} {{.Name}} { {{range $k,$v := .AddedElements}}{{$k}} : {{$v}}, {{end}} }
{{end}}{{end}}
{{range .Sets}}{{if .AddedElements}}add element {{$family}} {{$tableName}} {{.Name}} { {{range $k,$v := .AddedElements}}{{$k}}, {{end}} }
{{end}}{{end}}

View File

@@ -46,6 +46,7 @@ package nftables
import (
"bytes"
"context"
_ "embed"
"errors"
"fmt"
"iter"
@@ -357,100 +358,6 @@ func (t Table) Family() Family {
return t.t.Family
}
// incrementalUpdateTemplText is used with text/template to generate an nftables command file
// (which will be applied atomically). Updates using this template are always incremental.
// Steps are:
// - declare the table and its sets/maps with empty versions of modified chains, so that
// they can be flushed/deleted if they don't yet exist. (They need to be flushed in case
// a version of them was left behind by an old incarnation of the daemon. But, it's an
// error to flush or delete something that doesn't exist. So, avoid having to parse nft's
// stderr to work out what happened by making sure they do exist before flushing.)
// - if the table is newly declared, flush rules from its chains
// - flush each newly declared map/set
// - delete deleted map/set elements
// - flush modified chains
// - delete deleted chains
// - re-populate modified chains
// - add new map/set elements
const incrementalUpdateTemplText = `{{$family := .Family}}{{$tableName := .Name}}
table {{$family}} {{$tableName}} {
{{range .Maps}}map {{.Name}} {
{{.ElementTypeExpr}}
{{if len .Flags}}flags{{range .Flags}} {{.}}{{end}}{{end}}
}
{{end}}
{{range .Sets}}set {{.Name}} {
{{.ElementTypeExpr}}
{{if len .Flags}}flags{{range .Flags}} {{.}}{{end}}{{end}}
}
{{end}}
{{range .Chains}}{{if .MustFlush}}chain {{.Name}} {
{{if .ChainType}}type {{.ChainType}} hook {{.Hook}} priority {{.Priority}}; policy {{.Policy}}{{end}}
} ; {{end}}{{end}}
}
{{if .MustFlush}}flush table {{$family}} {{$tableName}}{{end}}
{{range .Maps}}{{if .MustFlush}}flush map {{$family}} {{$tableName}} {{.Name}}
{{end}}{{end}}
{{range .Sets}}{{if .MustFlush}}flush set {{$family}} {{$tableName}} {{.Name}}
{{end}}{{end}}
{{range .Chains}}{{if .MustFlush}}flush chain {{$family}} {{$tableName}} {{.Name}}
{{end}}{{end}}
{{range .Maps}}{{if .DeletedElements}}delete element {{$family}} {{$tableName}} {{.Name}} { {{range $k,$v := .DeletedElements}}{{$k}}, {{end}} }
{{end}}{{end}}
{{range .Sets}}{{if .DeletedElements}}delete element {{$family}} {{$tableName}} {{.Name}} { {{range $k,$v := .DeletedElements}}{{$k}}, {{end}} }
{{end}}{{end}}
{{range .DeleteCommands}}{{.}}
{{end}}
table {{$family}} {{$tableName}} {
{{range .Chains}}{{if .MustFlush}}chain {{.Name}} {
{{if .ChainType}}type {{.ChainType}} hook {{.Hook}} priority {{.Priority}}; policy {{.Policy}}{{end}}
{{range .Rules}}{{.}}
{{end}}
}
{{end}}{{end}}
}
{{range .Maps}}{{if .AddedElements}}add element {{$family}} {{$tableName}} {{.Name}} { {{range $k,$v := .AddedElements}}{{$k}} : {{$v}}, {{end}} }
{{end}}{{end}}
{{range .Sets}}{{if .AddedElements}}add element {{$family}} {{$tableName}} {{.Name}} { {{range $k,$v := .AddedElements}}{{$k}}, {{end}} }
{{end}}{{end}}
`
// reloadTemplText is used with text/template to generate an nftables command file
// (which will be applied atomically), to fully re-create a table.
//
// It first declares the table so if it doesn't already exist, it can be deleted.
// Then it deletes the table and re-creates it.
const reloadTemplText = `{{$family := .Family}}{{$tableName := .Name}}
table {{$family}} {{$tableName}} {}
delete table {{$family}} {{$tableName}}
table {{$family}} {{$tableName}} {
{{range .Maps}}map {{.Name}} {
{{.ElementTypeExpr}}
{{if len .Flags}}flags{{range .Flags}} {{.}}{{end}}{{end}}
{{if .Elements}}elements = {
{{range $k,$v := .Elements}}{{$k}} : {{$v}},
{{end -}}
}{{end}}
}
{{end}}
{{range .Sets}}set {{.Name}} {
{{.ElementTypeExpr}}
{{if len .Flags}}flags{{range .Flags}} {{.}}{{end}}{{end}}
{{if .Elements}}elements = {
{{range $k,$v := .Elements}}{{$k}},
{{end -}}
}{{end}}
}
{{end}}
{{range .Chains}}chain {{.Name}} {
{{if .ChainType}}type {{.ChainType}} hook {{.Hook}} priority {{.Priority}}; policy {{.Policy}}{{end}}
{{range .Rules}}{{.}}
{{end}}
}
{{end}}
}
`
// SetBaseChainPolicy sets the default policy for a base chain. The update
// is applied immediately, unlike creation/deletion of objects via a [Modifier]
// which are not applied until [Modifier.Apply] is called.
@@ -1184,15 +1091,37 @@ func (c *chain) Rules() iter.Seq[string] {
}
}
// incrementalUpdateTemplText is used with text/template to generate an nftables command file
// (which will be applied atomically). Updates using this template are always incremental.
// Steps are:
// - declare the table and its sets/maps with empty versions of modified chains, so that
// they can be flushed/deleted if they don't yet exist. (They need to be flushed in case
// a version of them was left behind by an old incarnation of the daemon. But, it's an
// error to flush or delete something that doesn't exist. So, avoid having to parse nft's
// stderr to work out what happened by making sure they do exist before flushing.)
// - if the table is newly declared, flush rules from its chains
// - flush each newly declared map/set
// - delete deleted map/set elements
// - flush modified chains
// - delete deleted chains
// - re-populate modified chains
// - add new map/set elements
//
//go:embed incremental_update.nft.gotmpl
var incrementalUpdateTemplText string
// reloadTemplText is used with text/template to generate an nftables command file
// (which will be applied atomically), to fully re-create a table.
//
// It first declares the table so if it doesn't already exist, it can be deleted.
// Then it deletes the table and re-creates it.
//
//go:embed reload.nft.gotmpl
var reloadTemplText string
func parseTemplate() error {
var err error
incrementalUpdateTempl, err = template.New("ruleset").Parse(incrementalUpdateTemplText)
if err != nil {
return fmt.Errorf("parsing 'incrementalUpdateTemplText': %w", err)
}
reloadTempl, err = template.New("ruleset").Parse(reloadTemplText)
if err != nil {
return fmt.Errorf("parsing 'reloadTemplText': %w", err)
}
return nil
var errs [2]error
incrementalUpdateTempl, errs[0] = template.New("incremental_update.nft.gotmpl").Parse(incrementalUpdateTemplText)
reloadTempl, errs[1] = template.New("reload.nft.gotmpl").Parse(reloadTemplText)
return errors.Join(errs[:]...)
}

View File

@@ -0,0 +1,37 @@
{{- /*
This reload template is used with text/template to generate an nftables command
file (which will be applied atomically), to fully re-create a table.
It first declares the table so if it doesn't already exist, it can be deleted.
Then it deletes the table and re-creates it.
*/ -}}
{{$family := .Family}}{{$tableName := .Name}}
table {{$family}} {{$tableName}} {}
delete table {{$family}} {{$tableName}}
table {{$family}} {{$tableName}} {
{{range .Maps}}map {{.Name}} {
{{.ElementTypeExpr}}
{{if len .Flags}}flags{{range .Flags}} {{.}}{{end}}{{end}}
{{if .Elements}}elements = {
{{range $k,$v := .Elements}}{{$k}} : {{$v}},
{{end -}}
}{{end}}
}
{{end}}
{{range .Sets}}set {{.Name}} {
{{.ElementTypeExpr}}
{{if len .Flags}}flags{{range .Flags}} {{.}}{{end}}{{end}}
{{if .Elements}}elements = {
{{range $k,$v := .Elements}}{{$k}},
{{end -}}
}{{end}}
}
{{end}}
{{range .Chains}}chain {{.Name}} {
{{if .ChainType}}type {{.ChainType}} hook {{.Hook}} priority {{.Priority}}; policy {{.Policy}}{{end}}
{{range .Rules}}{{.}}
{{end}}
}
{{end}}
}