vitessio/vitess · error

failed to parse vtctld FQDN template %s: %w

Error message

failed to parse vtctld FQDN template %s: %w

What it means

NewConsul compiles the vtctld FQDN template with template.Parse; failure is wrapped with the raw template string and the parse error. This means the vtctld fully-qualified-domain-name template is not valid Go template syntax.

Source

Thrown at go/vt/vtadmin/cluster/discovery/discovery_consul.go:155

	}

	disco.vtgateAddrTmpl, err = template.New("consul-vtgate-address-template-" + cluster.Id).Parse(*vtgateAddrTmplStr)
	if err != nil {
		return nil, fmt.Errorf("failed to parse vtgate host address template %s: %w", *vtgateAddrTmplStr, err)
	}

	/* vtctld options */
	if *vtctldDatacenterTmplStr != "" {
		disco.vtctldDatacenter, err = generateConsulDatacenter("vtctld", cluster, *vtctldDatacenterTmplStr)
		if err != nil {
			return nil, fmt.Errorf("failed to generate vtctld consul datacenter from template: %w", err)
		}
	}

	if *vtctldFQDNTmplStr != "" {
		disco.vtctldFQDNTmpl, err = template.New("consul-vtctld-fqdn-template-" + cluster.Id).Parse(*vtctldFQDNTmplStr)
		if err != nil {
			return nil, fmt.Errorf("failed to parse vtctld FQDN template %s: %w", *vtctldFQDNTmplStr, err)
		}
	}

	disco.vtctldAddrTmpl, err = template.New("consul-vtctld-address-template-" + cluster.Id).Parse(*vtctldAddrTmplStr)
	if err != nil {
		return nil, fmt.Errorf("failed to parse vtctld host address template %s: %w", *vtctldAddrTmplStr, err)
	}

	return disco, nil
}

func generateConsulDatacenter(component string, cluster *vtadminpb.Cluster, tmplStr string) (string, error) {
	tmpl, err := template.New("consul-" + component + "-datacenter-" + cluster.Id).Parse(tmplStr)
	if err != nil {
		return "", fmt.Errorf("error parsing template %s: %w", tmplStr, err)
	}

	dc, err := textutil.ExecuteTemplate(tmpl, &struct {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Correct the template syntax so template.Parse succeeds.
  2. Mirror a known-good template, e.g. "{{ .Cluster.Id }}-vtctld.example.com".
  3. Empty the flag to skip the vtctld FQDN option entirely.
  4. Use the wrapped parse error to locate the bad token/offset.

Example fix

// before
vtctld_fqdn_template: "{{ .Cluster.Id }}-vtctld{{"
// after
vtctld_fqdn_template: "{{ .Cluster.Id }}-vtctld.example.com"
Defensive patterns

Strategy: validation

Validate before calling

if s := *vtctldFQDNTmplStr; s != "" {
    if _, err := template.New("check").Parse(s); err != nil {
        return fmt.Errorf("invalid vtctld FQDN template %q: %w", s, err)
    }
}

Try / catch

disco, err := NewConsul(cluster, args)
if err != nil && strings.Contains(err.Error(), "vtctld FQDN template") {
    log.Fatalf("fix vtctld FQDN template: %v", err)
}

Prevention

When it happens

Trigger: Calling NewConsul (through discovery.New("consul", ...)) with a non-empty vtctld FQDN template flag containing syntax errors such as unbalanced braces, stray text/template operators, or calls to unregistered functions.

Common situations: Typo when duplicating the vtgate template for vtctld, quoting issues in YAML/env, referencing fields absent from vtadminpb.Cluster.

Understand the failure class

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/5d149679c4cfef9c. Report an issue: GitHub.