Как я могу условно установить переменную в шаблоне Go на основе выражения, которое может вызвать ошибку, если оно не заключено в оператор if

Вопрос

Как мне сделать что-то вроде этого:

{{ $use_ssl := (ne $.Env.CERT_NAME "") }}

где $.Env.CERT_NAME может быть нулем / неопределенным. Если он равен нулю, выдается следующая ошибка:

at <ne $.Env.CERT_NAME "">: error calling ne: invalid type for comparison

Примечание. У меня нет контроля над объектами, переданными в шаблон Go, поэтому мне приходится решать эту проблему полностью в самом шаблоне.

Что я пробовал

Я попытался обойти эту проблему, сначала проверив, не является ли он пустым:

{{ $use_ssl := ( ($.Env.CERT_NAME) && (ne $.Env.CERT_NAME "") ) }}

но это дает эту ошибку:

unexpected "&" in operand

Итак, я переключился на это, что разрешено синтаксически:

{{ $use_ssl := (and ($.Env.CERT_NAME) (ne $.Env.CERT_NAME "") ) }}

но тогда я теряю оценку короткого замыкания, который я получил бы с оператором &&, и я снова получаю эту ошибку:

at <ne $.Env.CERT_NAME ...>: error calling ne: invalid type for comparison

Хорошо, подумал я, если я не смогу сделать это красивым однострочником, и Go не имеет тернарный оператор, это нормально, я просто сделаю это идиоматическим способом Go, который, очевидно, if/else .

{{ if $.Env.CERT_NAME }}
  {{ $use_ssl := (ne $.Env.CERT_NAME "") }}
{{ else }}
  {{ $use_ssl := false }}
{{ end }}

Но тогда, конечно, у меня возникают проблемы с областью видимости, потому что if необъяснимо (или, по крайней мере, досадно) создает новую область видимости переменной (в отличие от шаблонов Ruby / ERB, к которым я больше привык), поэтому, очевидно, я даже не могу этого сделать:

{{ if $.Env.CERT_NAME }}
  {{ $use_ssl := true }}
{{ else }}
  {{ $use_ssl := false }}
{{ end }}
# $use_ssl: {{ $use_ssl }}

без получения этой ошибки сейчас:

undefined variable "$use_ssl"

«Без пота», - подумал я. Я просто объявлю переменную во внешней области видимости, чтобы она имела правильную область видимости, а внутренняя область все еще могла изменять эту переменную (ту, которая имеет внешнюю область видимости). (Кстати, именно так это работает в Ruby.)

Нет, по-видимому, все, что нужно сделать, это создать 2 разные переменные с одинаковым именем, но с разными областями действия (как это запутать!):

{{ $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

Я также пробовал вставить оператор if следующим образом:

{{ $use_ssl := if $.Env.CERT_NAME { true } }}

но это дает эту ошибку:

unexpected <if> in command

Очевидно, операторы if нельзя использовать в качестве выражений в Go, как в Ruby?

Я также получаю синтаксические ошибки, если попробую различные альтернативы тернарному оператору, предложенные в Что такое идиоматический эквивалент троичного оператора C в Go?, например:

c := map[bool]int{true: 1, false: 0} [5 > 4]

И конечно, вы можете читать переменные из внешних областей во внутренних областях с помощью $.something, но как вы установить $.something во внутренней области?

Если не так, то как ???

Так что я упустил? Возможно ли это вообще в шаблоне Go?

Шаблоны Go (я новичок в них) кажутся очень ограничивающими. Практически все, что вы можете делать в Go, невозможно в шаблоне. Но, надеюсь, есть обходной путь ...

У http://play.golang.org/p/SufZdsx-1v есть умный обходной путь включая создание нового объекта с {{$hasFemale := cell false}}, а затем установку значения с $hasFemale.Set true, но как я могу сделать что-то подобное, не имея доступа к коду, который оценивает шаблон (где они вызывают template.FuncMap)?

Другие, которые пытались и потерпели неудачу

https://groups.google.com/forum/#!topic/golang-nuts/MUzNZ9dbHrg

Это самая большая (и единственная) проблема с Go. Я просто не понимаю, почему до сих пор не реализован этот функционал.

Когда пакет шаблона используется в общем контексте (например, при выполнении произвольного файла шаблона с произвольным файлом json), у вас может не быть возможности выполнить предварительные вычисления.

https://github.com/golang/go/issues/10608

Вероятно, это так, как задумано, но было бы очень хорошо, если бы существовал способ получить измененный $ v за пределами условного оператора.

Это шаблонный вопрос №1, который мы решаем в Hugo (https://github.com/spf13/hugo). Мы добавили хак [$.Scratch.Set "v1" 123], чтобы пока обойти это ...


person Tyler Rick    schedule 24.03.2016    source источник
comment
Спасибо за отличный обзор, я проделал подобное путешествие и теперь наконец понимаю, почему {{ if ... }} assignment here {{ end }} не удалось!   -  person consideRatio    schedule 07.04.2018


Ответы (3)


В настоящее время рабочее решение

Если вы обновляете область действия, а не объявляете переменную, используя set или merge, вы сможете получить к ней доступ за пределами предложения if.

{{- 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 }}

Будущее решение (доступно с Helm 2.10)

Я столкнулся с тем же вопросом в контексте шаблонов Helm, которые представляют собой шаблоны go с некоторыми предустановленными дополнительными функциями, такими как Веточка.

28 марта 2018 г. в этом PR к Sprig был добавлен оператор Terenary, а со временем Helm они также решат проблему и у вас, и у меня.

теренары

true | ternary "b" "c" вернет "b"

false | ternary "b" "c" вернет "c"

person consideRatio    schedule 02.04.2018
comment
Интересно. Не то чтобы он доступен в простых шаблонах Go, но если бы это было так, как бы я использовал его для обработки этого случая, когда $cert_name может быть нулевым? Я предполагаю, что нулевой ввод считается ложным? {{ $use_ssl := (ne $cert_name "") }} Так что, может быть, что-то вроде этого для обработки нулевого случая? {{ $use_ssl := (ne ($cert_name | ternary $cert_name "") "") }} - person Tyler Rick; 06.04.2018
comment
Если у вас есть функции веточки, вы также можете использовать их функцию по умолчанию: masterminds.github.io /sprig/defaults.html (вы можете использовать его с каналами, как и $myPerhapsNullVar | default "defaultValue") - person consideRatio; 07.04.2018
comment
Пользователи Helm также могут сделать что-нибудь сложное с функцией tpl: github.com/kubernetes/helm/pull/ 2350 - person consideRatio; 08.07.2018

Я думаю, что, возможно, нашел обходной путь для моей конкретной проблемы, хотя мне хотелось бы услышать другие ответы, которые решают проблему в более общем плане.

Наткнулся на эту строку в https://github.com/jwilder/nginx-proxy/blob/1e0b930/nginx.tmpl#L91:

{{ $default_host := or ($.Env.DEFAULT_HOST) "" }}

что дало мне еще одну идею попробовать. Думаю, решение состоит в том, чтобы использовать комбинацию or и установить дополнительные временные переменные ...

{{ $cert_name := or $.Env.CERT_NAME "" }}
{{ $use_ssl := ne $cert_name "" }}
person Tyler Rick    schedule 24.03.2016
comment
Официальная документация по использованию значений по умолчанию: github.com/hashicorp/consul-template#env - person Vikas; 22.01.2020

Четвертая попытка сработает, если вы будете использовать оператор присваивания вместо оператора определения:

{{ $use_ssl := false }}
{{ if $.Env.CERT_NAME }}
  {{ $use_ssl = true }} # notice the missing colon!
{{ else }}
  {{ $use_ssl = false }}
{{ end }}
person itmuckel    schedule 06.11.2019
comment
К сожалению, не работает: Unable to parse template: unexpected "=" in operand - person Guicara; 16.01.2020