a73x

internal/server/api/networks.go

Ref:   Size: 2.9 KiB   History

package api

import (
	"fmt"
	"strings"

	"github.com/a73x/eitri/internal/names"
)

// noNetworkRefusal explains a create naming a network its host is not
// advertising, with the remedy in the operator's vocabulary. Which remedy
// depends on what the host said: its OS cannot serve one at all (name a
// Linux host instead), nothing at all (wait for it), no networks (configure
// one), or other names (pick one, or configure this one). Every branch ends
// at the same escape hatch — the NAT underlay is always there.
//
// os is store.Host.OS, an enrollment-time fact: it is written once, at
// RedeemEnrollmentToken, from the joining agent's runtime.GOOS, and
// UpdateHostFacts (the per-Hello refresh) deliberately never touches it — so
// it needs no spoken/hostHasSpoken gate the way advertised networks do, a
// kernel does not change OS between Hellos. An empty os is not a host that
// has gone quiet; it is an enroll request that omitted the field (handleEnroll
// passes req.OS through unvalidated). That falls through to the existing
// branches rather than being told it cannot serve anything: unknown is not
// the same claim as known-incapable, and only the latter is worth
// interrupting the operator over.
//
// The comparison stays un-normalised on purpose: os is trusted to be a
// literal runtime.GOOS string, because the only thing that ever writes it is
// the agent's own enroll request, and every real agent sends exactly what
// runtime.GOOS gives it. A malformed value here (say "Linux", capitalized)
// would be a different failure than the ones this function is for — an
// enroller lying about its platform — and OSServesNamedNetworks would refuse
// it exactly like an unsupported OS: permanently, with no flag able to fix
// it. That is the right failure mode for a value nothing legitimate can
// produce, so it gets no special-cased remedy of its own.
func noNetworkRefusal(hostName, hostID, network, os string, advertised []string, spoken bool) string {
	var b strings.Builder
	fmt.Fprintf(&b, "host %q (%s) is not advertising network %q", hostName, hostID, network)
	switch {
	case os != "" && !names.OSServesNamedNetworks(os):
		fmt.Fprintf(&b, ": its OS (%s) cannot serve named networks. Place bridged guests on a Linux host, or create without a network for the NAT underlay.", os)
	case !spoken:
		b.WriteString(": it is not currently reporting, so its networks cannot be confirmed. Wait for the host to come online, or create without a network for the NAT underlay.")
	case len(advertised) == 0:
		b.WriteString(": its agent is configured with no named networks. Restart the agent with --host-network " + network + "=<bridge>, or create without a network for the NAT underlay.")
	default:
		fmt.Fprintf(&b, ": it advertises %s. Pick one of those, restart the agent with --host-network %s=<bridge>, or create without a network for the NAT underlay.", strings.Join(advertised, ", "), network)
	}
	return b.String()
}