How to do macro-like templating with parameters in dynamic configuration?

In Apache, one can do something like the following in a config file using the Macro directive:

<Macro AliasHost $zone $alias $target>
  <VirtualHost *:80>
      ServerName $alias
      ServerAlias www.$alias

      Redirect permanent / $target
  </VirtualHost>

  <VirtualHost *:443>
      ServerName $alias
      ServerAlias www.$alias

      Redirect permanent / $target

      Include conf/includes/$zone-ssl-config.conf
  </VirtualHost>
</Macro>

Use AliasHost example sub.example.com https://target.example.com/

I am led to believe that Go templating in Traefik's dynamic file config should offer me the same or similar functionality, however I have been unable to get this style of templating using {{define}} and {{template}} to work with multiple parameters in a similar manner. The Go docs for text/template are a bit too dense for me; I haven't written any Go before so I really just need a working example to copy.

I have been successful at producing a working minimal example with one unnamed parameter as follows:

{{define "proxrouter"}}
    prox-{{ . }}:
      rule: Host(`prox.{{ env "DNS_ZONE" }}`) && PathPrefix(`/{{ . }}`)
      middlewares:
        - proxyChain
      service: svcprox-{{ . }}
{{end}}

http:
  routers:
{{template "proxrouter" "test"}}

However I have been unable to figure out how to pass named or multiple parameters, for example:

Named parameter (made-up example):

{{define "proxrouter"}}
    prox-{{ $name }}:
      rule: Host(`prox.{{ env "DNS_ZONE" }}`) && PathPrefix(`/{{ $name }}`)
      middlewares:
        - proxyChain
      service: svcprox-{{ $name }}
{{end}}

http:
  routers:
{{template "proxrouter" ???}}

Multiple parameters (made-up example):

{{define "proxrouter"}}
    prox-{{ .Name }}:
      rule: Host(`prox.{{ env "DNS_ZONE" }}`) && PathPrefix(`/{{ .Path }}`)
      middlewares:
        - proxyChain
      service: svcprox-{{ .Name }}
{{end}}

http:
  routers:
{{template "proxrouter" ???}}

I really don't understand the Go template "pipeline", but this seems like it should be trivially possible. Unfortunately all of my googling results in information about rendering a template from a Go program.

I have tried a few things to fill the ??? slots above in this Go playground:

... but they all fail with a nil pointer dereference error, which in Traefik appears to present as log output similar to the following:

time="2022-03-31T02:57:18Z" level=error msg="Error occurred during watcher callback: /etc/traefik/conf/dynamic/proxy.yml: unsupported simple value type: invalid" providerName=file
time="2022-03-31T03:06:34Z" level=error msg="Error occurred during watcher callback: /etc/traefik/conf/dynamic/proxy.yml: template: :19:36: executing \"\" at <.Key>: can't evaluate field Key in type bool" providerName=file

For the multiparam option I was trying to follow the suggestion here:

I think I figured this out on my own. The StackOverflow answer I referenced requires outside code, but this dict function is actually provided by the Sprig functions which are included by Traefik. So this playground works:

(Note you can use this to play with Traefik template style, however backticks are not allowed or escapable within a go raw string literal so either remove them or use the hacky workaround shown in the playground example.)

My understanding of how the "pipeline" works when providing an argument to {{template}} is that this argument by definition has to be anonymous/unnamed. So if you want to call something by name within your {{define}}d template, you have two options:

  1. Give the pipeline value a name within the {{define}} block
{{define "proxrouter"}}
    {{ $name := . }}
    prox-{{ $name }}:
      rule: Host(`prox.{{ env "DNS_ZONE" }}`) && PathPrefix(`/{{ $name }}`)
      middlewares:
        - proxyChain
      service: svcprox-{{ $name }}
{{end}}

http:
  routers:
{{template "proxrouter" "pipeline_value"}}
  1. Use the dict Sprig function
{{define "proxrouter"}}
    prox-{{ .Name }}:
      rule: Host(`prox.{{ env "DNS_ZONE" }}`) && PathPrefix(`/{{ .Name }}`)
      middlewares:
        - proxyChain
      service: svcprox-{{ .Name }}
{{end}}

http:
  routers:
{{template "proxrouter" dict "Name" "dict_value"}}

Multiple parameters are accomplished simply by adding further name-value pairs as arguments to dict per the docs:

1 Like

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.