How do I do something like this:
{{ $use_ssl := (ne $.Env.CERT_NAME "") }}
where $.Env.CERT_NAME
may be nil/undefined. If it is nil, it gives this error:
at <ne $.Env.CERT_NAME "">: error calling ne: invalid type for comparison
Note: I have no control over the objects passed in to the Go template so have to solve this entirely within the template itself.
I tried to work around by first checking to see if it is non-empty:
{{ $use_ssl := ( ($.Env.CERT_NAME) && (ne $.Env.CERT_NAME "") ) }}
but it gives this error:
unexpected "&" in operand
So I switched to this, which is allowed syntactically:
{{ $use_ssl := (and ($.Env.CERT_NAME) (ne $.Env.CERT_NAME "") ) }}
but then I lose the short-circuit evaluation that I would get with the &&
operator and I'm back to getting this error again:
at <ne $.Env.CERT_NAME ...>: error calling ne: invalid type for comparison
Okay, I thought, if I can't do it in a nice one-liner, and Go doesn't have a ternary operator, that's fine, I'll just do it the idiomatic Go way, which is apparently if/else
.
{{ if $.Env.CERT_NAME }}
{{ $use_ssl := (ne $.Env.CERT_NAME "") }}
{{ else }}
{{ $use_ssl := false }}
{{ end }}
But then of course I run into scoping issues, because if
inexplicably (or at least annoyingly) creates a new variable scope (unlike in Ruby/ERB templates that I am more used to), so apparently I can't even do this:
{{ if $.Env.CERT_NAME }}
{{ $use_ssl := true }}
{{ else }}
{{ $use_ssl := false }}
{{ end }}
# $use_ssl: {{ $use_ssl }}
without getting this error now:
undefined variable "$use_ssl"
"No sweat", I thought. I'll just declare the variable in the outside scope so it will have the correct scope and the inner scope will still be able to change that variable (the one with the outer scope). (That's how it works in Ruby, by the way.)
Nope, apparently all that does is create 2 different variables with the same name but different scopes (how's that for confusing!):
{{ $use_ssl := false }}
{{ if $.Env.CERT_NAME }}
{{ $use_ssl := true }}
# $use_ssl: {{ $use_ssl }}
{{ else }}
{{ $use_ssl := false }}
{{ end }}
# $use_ssl: {{ $use_ssl }}
# $use_ssl: true
# $use_ssl: false
I've also tried inlining the if statement like this:
{{ $use_ssl := if $.Env.CERT_NAME { true } }}
but that gives this error:
unexpected <if> in command
Apparently if
statements can't be used as expressions in Go like they can in Ruby?
I also get syntax errors if I try the various alternatives to the ternary operator suggested in What is the idiomatic Go equivalent of C's ternary operator?, like:
c := map[bool]int{true: 1, false: 0} [5 > 4]
And sure, you can read variables from outer scopes in inner scopes with $.something
but how do you set $.something
in the inner scope?
So what have I missed? Is this even possible in a Go template?
Go templates (I'm new to them) seem very limiting. Almost everything you can do in Go itself is not possible in a template. But hopefully there is a workaround...
http://play.golang.org/p/SufZdsx-1v has a clever workaround involving creating a new object with {{$hasFemale := cell false}}
and then setting a value with $hasFemale.Set true
, but how can I do something like that without having access to the code that evaluates the template (where they are calling template.FuncMap
)?
https://groups.google.com/forum/#!topic/golang-nuts/MUzNZ9dbHrg
This is the biggest (and only) problem I have with Go. I simply do not understand why this functionality has not been implemented yet.
When the template package is used in a generic context (eg. Executing an arbitrary template file with an arbitrary json file), you might not have the luxury of doing precalculations.
https://github.com/golang/go/issues/10608
This is probably as designed, but it would be really nice if there was a way to get the changed $v outside of the conditional.
This is the #1 template question we get over at Hugo (https://github.com/spf13/hugo). We have added a hack [
$.Scratch.Set "v1" 123
] to work around it for now ...
If you update the scope rather than declare a variable, either by using set
or by using merge
you will be able to access it outside of the if clause.
{{- if eq .podKind "core" -}}
{{- $dummy := set . "matchNodePurpose" .Values.scheduling.corePods.nodeAffinity.matchNodePurpose }}
{{- else if eq .podKind "user" -}}
{{- $dummy := set . "matchNodePurpose" .Values.scheduling.userPods.nodeAffinity.matchNodePurpose }}
{{- end -}}
# Will now output what you decided within an if/else if clause
{{- .matchNodePurpose }}
I came across the same question in the context of Helm templates which are go templates with some additional features pre-installed such as Sprig.
28 March 2018, in this pr a Terenary operator was added to Sprig, and in time Helm will have them as well solving the issue both you and I have.
terenary
true | ternary "b" "c"
will return "b"
false | ternary "b" "c"
will return "c"