Closed cvarnerin closed 9 years ago
Hrm... I think this is because we are returning a slice that has 0 elements in it like:
result := make([]*dep.HealthService, 0)
But I think you should be able to get the index of that item... The problem is that we can't give that slice a size or else it won't work during nested queries.
The first time CT runs, it doesn't have data for the services, so it just returns an empty slice. It looks like Go's index
function does not like that...
@cvarnerin While it does seem strange at first that you get an out-of-range error, if you think about it, it does make sense. The thing you are trying to index into (in this case, services
), may or may not have data at any given time, not only at consul-template's startup. This means that providing an explicit, non-dynamic index will always have potential to cause this error, since the index is dependent on the received data from consul. The map works because accessing a non-existent map key just returns nil
rather than raising an error.
Because of this, to me it seems it would be a mistake to hard-code an index this way for any type of data from consul. If you need to use known indexes, I would recommend using the range $index, $element := pipeline
syntax, and check the index.
@cvarnerin to follow up on what @ryanuber said, even if we provided our own index
function that permitted a 0-index and returned nil
if the resource didn't exist (like in the map
case), you would immediately get a nil error when you try to call any properties or functions on the resource.
{{ with index (service "webapp") 0 }}
{{.Name}} // This will error because the service definition is nil
{{ end }}
The reason the range + index works is because range
won't iterate over an empty slice.
@sethvargo and @ryanuber - I agree it is wrong to hard-code a static index as provided in the first example, but there are plenty of reasonable uses for the index function. e.g.
{{ if $webapp := service "webapp" }}
{{ index $webapp (index function) }}
// do something
{{ end }}
Where (index function)
is something like (modulo $random (len $webapp))
. Of course, the same could be accomplished with:
{{ $index, $element := range service "webapp" }}
{{ if (eq $index (index function)) }}
// do something
{{ end }}{{end}}
Both forms seem similar in complexity and readability, so is there any reason not to support the index
global function? If it will remain unsupported, where would this behavior be best documented? Is there a CT project IRC channel? Thanks for your responses!
@cvarnerin I don't think we are explaining clearly enough. We aren't choosing not to support the index
function - we can't support it. Go does not permit you to access the 0th element in an empty slice. For an empty slice, (modulo $random (len $webapp))
would still return 0, thus returning an error. While part of @ryanuber's argument was "you shouldn't be doing that anyway", it should not be misconstrued with "we won't do it". Does that makes sense? We are not intentionally limiting any functionality to discourage this use case - index
is a native Go template function and this is the behavior of slices in Go.
As for "random", you might be interested in my response to this ticket as to why accessing a random element in the slice would be terribly troublesome.
You can use #consul
on freenode for Consul and related projects such at CT.
@sethvargo is right, we can't support unguarded slice access by index. It will be a problem for the initial consul-template start-up, and even if we could get past that, it could case problems later if the slice became empty later on.
@cvarnerin I actually think index
should work as expected when it is guarded by a if
condition. The problem with using index
without the guard is that consul-template needs to try rendering your templates before it has even gotten any data in consul, thus causing the out-of-range errors. With that said, you should definitely take proper precautions when trying to simulate randomly selected nodes or services, as @sethvargo pointed out and linked to.
@sethvargo I think @ryanuber stated my thoughts better than I did, namely:
index
should work as expected when it is guarded by anif
condition
I tested the "guarded" example provided above and I can confirm the index function works as expected. Thanks for walking me through the first pass edge case.
Interesting thread on "random". I've been thinking about implementation and it's certainly non-trivial. Maybe something like return the hash of a semi-constant token like temp system file, consul-template PID, hostname, etc. This will guarantee a "random" value that doesn't change between passes of CT but shouldn't be the same across all nodes.
Calling the index global function in consul-template returns errors when called on a non-empty service slice (
[]*service
), but works on maps returned bybyTag
.Reproduce issue with
index
function on[]*service
:Whereas
index
function working with maps:Which returns a
[]*service
as expected.