Go Templates - Check whether a key exists in json list

10/7/2016

I am using confd for a dynamic nginx service reconfiguration and having a bit of a struggle with the Go Templates. I have a json object named $data and $data.subsets may contain a key named 'addresses' -- I want to only execute the template inside the loop if that condition is met.

I've tried various things like the $data.subsets[0].addresses you see below which is completely wrong. I am not really sure how this can be done correctly.

This is a piece of my nginx template that I only want to render if there is at least 1 upstream service, not if there are none/unavailable.

Following the template is the json objects for $data on success any failure.

Thank you in advance for the help, it's greatly appreciated!

{{ range $ns := getvs "/registry/services/endpoints/*/app" }}
{{ $data := json $ns }}
    {{ if $data.subsets[0].addresses }}
upstream {{ $data.metadata.namespace }}_{{ $data.metadata.name }}_pool {
        {{ range $subset := $data.subsets }}{{ range $ref := $subset.addresses }}{{ range $portConfig := $subset.ports }}
 server {{ $ref.ip }}:{{ $portConfig.port }};
        {{ end }}{{ end }}{{ end }}
}
    {{ end }}
{{ end }}

Example of $data when there are no Pods scheduled:

{  
   "kind":"Endpoints",
   "apiVersion":"v1",
   "metadata":{  
      "name":"app",
      "namespace":"ns1",
      "selfLink":"/api/v1/namespaces/ns1/endpoints/app",
      "labels":{  
         "app":"app"
      }
   },
   "subsets":[]
}

Example of $data when there are pods running but not healthy:

{  
   "kind":"Endpoints",
   "apiVersion":"v1",
   "metadata":{  
      "name":"app",
      "namespace":"ns1",
      "labels":{  
         "app":"app"
      }
   },
   "subsets":[  
      {  
         "notReadyAddresses":[  
            {  
               "ip":"10.254.60.5",
               "targetRef":{  
                  "kind":"Pod",
                  "namespace":"ns1",
                  "name":"app-421757659-83rfg",
               }
            }
         ],
         "ports":[  
            {  
               "name":"ns1-app-8080",
               "port":8080,
               "protocol":"TCP"
            }
         ]
      }
   ]
}

Example of $data when there are pods running and available. This is the only condition I wish to meet for the iteration of the template to render.

{  
   "kind":"Endpoints",
   "apiVersion":"v1",
   "metadata":{  
      "name":"app",
      "namespace":"ns1",
      "selfLink":"/api/v1/namespaces/ns1/endpoints/app",
      "labels":{  
         "app":"app"
      }
   },
   "subsets":[  
      {  
         "addresses":[  
            {  
               "ip":"10.254.18.11",
               "targetRef":{  
                  "kind":"Pod",
                  "namespace":"ns1",
                  "name":"app-3869480132-kfthi"
               }
            },
            {  
               "ip":"10.254.18.9",
               "targetRef":{  
                  "kind":"Pod",
                  "namespace":"ns1",
                  "name":"app-3869480132-9bufk"
               }
            }
         ],
         "ports":[  
            {  
               "name":"ns1-app-8080",
               "port":8080,
               "protocol":"TCP"
            }
         ]
      }
   ]
}
-- David Houde
confd
coreos
go
go-templates
kubernetes

1 Answer

10/8/2016

The $data.subsets[0].addresses is illegal template expression (contains [ and ]). Try the following template definition:

{{range $cfg := getvs "/registry/services/endpoints/*/app"}}
{{$data := json $cfg}}
{{range $data.subsets}}{{range $addr := .addresses}}
{{with $ns := $addr.targetRef.namespace}}
upstream {{$ns}}_{{$addr.targetRef.name}}_pool {
    {{range $data.subsets}}{{range $port := .ports}}
        {{/* 
            Split port.name then test if address namespace 
            match the port.name. Test Condition:
            len($portDesc) > 0 && $portDesc[0] == $ns
        */}}
        {{$portDesc := split $port.name "-"}}
        {{if and (gt (len $portDesc) 0) (eq (index $portDesc 0) $ns)}}
  server {{ $addr.ip }}:{{ $port.port }};
        {{end}}
    {{end}}{{ end }}
}
{{end}}
{{end}}{{end}}

Generated output(after removing empty lines) for the above three json data should be:

upstream ns1_app-3869480132-kfthi_pool {
  server 10.254.18.11:8080;
}
upstream ns1_app-3869480132-9bufk_pool {
  server 10.254.18.9:8080;
}

The key is range action in {{range $data.subsets}}{{range $addr := .addresses}}. Nothing will be written if len of pipeline (subsets or addresses) is zero, i.e. when subsets does not contains element or addresses is null (no subsets element named addresses).

-- putu
Source: StackOverflow