hashicorp / consul-template

Template rendering, notifier, and supervisor for @HashiCorp Consul and Vault data.
https://www.hashicorp.com/
Mozilla Public License 2.0
4.76k stars 781 forks source link

index on service #83

Closed bryanlarsen closed 9 years ago

bryanlarsen commented 9 years ago

Suppose I would like to get the IP address of any consul agent. I would expect something like this to work:

 {{ index (service "consul") 0 }}

What am I doing wrong? Thanks.

sethvargo commented 9 years ago

@bryanlarsen what is the use case here? Any reason you couldn't do something like:

service("consul")[0].IP

You are definitely opening yourself up to a panic there though...

bryanlarsen commented 9 years ago

Use case: starting registrator (and similar apps), which take as their arguments a single consul URL.

I tried what you suggest (surrounded by {{}}) and I get: [ERR] template: out:1: unexpected "(" in operand; missing space?. Adding spaces judiciously will get me past that but I can't get past unexpected "[" in operand; missing space?

The reason I'm trying 'index' is because that's what is mentioned in the text/template package documentation, there's no mention of a [] operator.

In my vagrant test box there's only a single consul running, so http://{{ range service "consul-8500" }}{{ .Address }}:{{ .Port }}{{ end }} works fine. However, there will presumably be more than one consul running in a production environment, so I assume that would produce a very strange URL in that situation.

As far as the panic, this appears to work, and should prevent the panic.

{{ if gt ( len ( service "consul-8500" ) ) 0 }}

thanks for your help!

sethvargo commented 9 years ago

Awesome!

bryanlarsen commented 9 years ago

Sorry, the main issue hasn't been solved. I think I have a solution for the 'panic problem you raised', but I still cannot get the address or port of a single service.

sethvargo commented 9 years ago
{{range $index, $element := service "consul"}}
{{if $index == 0}}{{.IP}}{{end}}
{{end}}

There's also the index function:

index
    Returns the result of indexing its first argument by the
    following arguments. Thus "index x 1 2 3" is, in Go syntax,
    x[1][2][3]. Each indexed item must be a map, slice, or array.
bryanlarsen commented 9 years ago

That doesn't work, but this does:

{{range $index, $element := service "consul"}}
{{if eq $index 0}}{{.Address}}{{end}}
{{end}}

Thanks!

Also, the index function is what I was trying originally, that didn't work.

blalor commented 9 years ago

I also ran across this recently. I'd like to know why the index function (pipeline?) doesn't work…

nik-johnson-net commented 2 years ago

This is old and closed, but certainly still a "gotcha". I spent an hour trying to figure out what I was doing wrong, only to realize this is a known issue.

eikenb commented 2 years ago

Hey @nik-johnson-net, is the issue you were having a similar case to the original issue report here? Ie. something like {{ index (service "consul") 0 }}?

nik-johnson-net commented 2 years ago

Yes, exactly the same issue.

eikenb commented 2 years ago

I've figured out what's going on here and why {{ index (service "consul") 0 }} doesn't work.

Consul-template uses a multi-pass rendering strategy which means that templates need to work on the first pass with no data as that pass is used to start the async lookups in Consul (et al.).

So if you use index without checking the length it will always fail as the first time it will be called it will always have a 0 length.

The simplest way to deal with this is with a conditional around the index. Eg. (formatted for readability, not usefulness)

{{ $svs := (service "consul") }}
{{ if gt (len $svs) 0 }}
    {{ index $svs 0 }}
{{end}}

There was a proposal (to golang) at one point to have index return nil in these cases which would have fixed this issue without requiring the test, but it was rejected as it made the template behavior different from the language behavior.

eikenb commented 2 years ago

That golang proposal... https://github.com/golang/go/issues/14751