diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index f5db7e158..f385ce312 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -247,6 +247,9 @@ func (e Engine) initFunMap(t *template.Template) { funcMap["getHostByName"] = func(_ string) string { return "" } + funcMap["checkHostByName"] = func(_ string) string { + return "" + } } // Set custom template funcs diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go index a97f8f104..8c888772c 100644 --- a/pkg/engine/funcs.go +++ b/pkg/engine/funcs.go @@ -60,6 +60,7 @@ func funcMap() template.FuncMap { "mustToJson": mustToJSON, "fromJson": fromJSON, "fromJsonArray": fromJSONArray, + "checkHostByName": checkHostByName, // This is a placeholder for the "include" function, which is // late-bound to a template. By declaring it here, we preserve the @@ -232,3 +233,34 @@ func fromJSONArray(str string) []interface{} { } return a } + +// checkHostByName resolves a hostname to its first IP address. +// +// - If the host is found, it returns the IP address. +// - If the host does not exist (NXDOMAIN), it returns an empty string. +// - If DNS fails for other reasons (SERVFAIL, timeout, etc.), it returns an error and aborts rendering. +// +// This function was introduced because Sprig's getHostByName silently swallows DNS errors. +// It retries DNS resolution up to 3 times on transient errors, with a 15-second delay between attempts. +func checkHostByName(name string) (string, error) { + var lastErr error + for tries := 3; tries > 0; tries-- { + addrs, err := net.LookupHost(name) + if err == nil && len(addrs) > 0 { + return addrs[0], nil + } + lastErr = err + + dnsErr, ok := err.(*net.DNSError) + if !ok { + return "", fmt.Errorf("checkHostByName: DNS error: %v", err) + } + if dnsErr.IsNotFound { + return "", nil // NXDOMAIN is not considered fatal + } + + time.Sleep(15 * time.Second) + } + + return "", fmt.Errorf("checkHostByName: DNS resolution failed for %s: %v", name, lastErr) +}