How can I avoid using stale config from a dead bind member?

Given a package with the below optional binds:

pkg_binds_optional=(
  [be]="port"
  [de]="port"
  [es]="port"
  [default]="port"
)

And bindees that satisfy those bindings by exporting config such as:

"cfg": {
  "dedicated_market": true,
  "healthcheck": "/health/heartbeat",
  "port": 80,
  "server_name": "www.mysite.be",
  "server_aliases": [
    "mysite.be"
  ]
}

I would like to be able to loop any/all binds to generate config conditionally based on the cfg values exported.

{{#each bind as |key value|~}}
  {{#if value.first.cfg.dedicated_market}}
    // do something with {{value.first.cfg.server_name}}
  {{/if~}}
{{~/each}}

However, value.first here often gives me a ‘dead’ member which is exporting stale config. I realise that I could loop only the members of the bind considered to be ‘alive’ as below.

{{#eachAlive bind.de.members as |member|}}
  {{#if member.cfg.dedicated_market}}
    // do something with {{member.cfg.server_name}}
  {{/if}}
{{/eachAlive}}

But the above solution has two flaws. First, it requires a templating block for every named bind and I’m keen to avoid maintaining a list of binds in both plan.sh and my config files(s). Second, it will print the same line of config for every ‘alive’ member since I cannot break out of the loop in handlebars, for example if I have four members considered to be alive, my config would look like this:

// do something with www.mysite.de
// do something with www.mysite.de
// do something with www.mysite.de
// do something with www.mysite.de

So I’m searching for a way of retrieving a single member of a bind that’s considered to be alive, and I’m eager to be able to do it inside an {{#each bind as |key value|}} block so I can loop all binds without explicitly naming them.

For the curious, my use case is that I want to use service discovery to register multiple HAProxy backends in my haproxy.conf. Those services that bind as a named (dedicated) group, e.g. de, be, are expected to provide a server_name that HAProxy will use to target them (through Host header-based ACL rules). Those services that bind as the ‘default’ group will form the default_backend in haproxy.conf.

What version of Hab are you running?

hab 0.59.0/20180712162348

Would this work?

{{#each bind as |key value|~}}
  {{#eachAlive value.members as |member| }}
    {{#if member.cfg.dedicated_market}}
      // do something with {{member.cfg.server_name}}
    {{/if}}
  {{/eachAlive}}
{{~/each}}

Thanks for your time @elliott-davis. That would get me halfway there, but will still repeat the //do something line(s) for as many alive members are found.

@iskorptix has pointed out the @first syntax, which can be used inside eachAlive to loop ALL alive members but only perform an action for the first result, which will hopefully solve my problem.

I suspect you may be running into https://github.com/habitat-sh/habitat/issues/5325

You shouldn’t actually have access to dead service members in your templates, but because of the Supervisor/Service health conflation mentioned in that issue, you may be accessing information from a Service that is no longer running, but the Supervisor it was running on is still running.

Thanks @christophermaier, that’s good to know for the future. Between my previous reply and your comment I have a workaround and long-term solution, so I’m a happy man.

@oliversellers Good to hear; would you mind posting your workaround here for any community members that are running into the same issue before we get that bug fixed?

Thanks!

Sure @christophermaier. The below syntax loops all binds and takes an action, once, for each service group, while guaranteeing that an ‘alive’ service group member will be used to provide the config. Note that there’s an extra nested loop since the config value I am interested in is a list.

{{~#each bind as |key value|~}}
{{~#eachAlive value.members as |member|~}}
{{#if @first}}
{{#each member.cfg.ports as |port|}}
frontend {{../../key}}_{{../../member.service}}_{{port}}
  bind *:{{port}}
  use_backend {{../../key}}_{{../../member.service}}_{{port}}
{{/each}}
{{/if}}
{{/eachAlive}}
{{/each}}

To do the same thing (get config for a service group member that’s guaranteed to be alive), but then proceed to loop all service group members with that config, you can do:

{{~#each bind as |key value|~}}
{{~#eachAlive value.members as |member|~}}
{{#if @first}}
{{#each member.cfg.ports as |port|~}}
{{#eachAlive ../../value.members as |member|~}}
{{#if @first}}
backend {{../../key}}_{{../../member.service}}_{{port}}
{{/if}}
  # servers
  server {{../../member.sys.ip}} {{../../member.sys.ip}}:{{port}}
{{/eachAlive}}
{{/each}}
{{/if}}
{{/eachAlive~}}
{{/each~}}