diff options
author | Todd Malsbary <todd.malsbary@intel.com> | 2021-03-01 15:13:08 -0800 |
---|---|---|
committer | Todd Malsbary <todd.malsbary@intel.com> | 2021-05-04 14:41:48 -0700 |
commit | 61dc8e7b3aa13852dfde84dad4e6152178dd298d (patch) | |
tree | 52ef38d3b13df5355517ae54a51736a8a6d919ff /kud/deployment_infra/helm/ovn4nfv/templates | |
parent | c7cc63c3e76d5739c67314008b01f6fe70289641 (diff) |
Add ovn4nfv addon helm chart
This chart follows the upstream installation guide with the following
exceptions:
- The node-role.kubernetes.io/master:NoSchedule taint is not removed.
The YAML files already included the necessary tolerations.
- No node labeling is done. Instead, the ovn-control-plane node
selector is for the master role, and the nfn-operator pod affinity
is for "role: ovn-control-plane". This ensures that the
ovn-control-plane and nfn-operator run are scheduled on the same
master node, equivalent to the labelling approach used upstream.
Also, additional allowed capabilities are needed to run the pods with
the restricted PodSecurityPolicy. These capabilities are requested by
the Pods, but not available in the default set of allowed
capabilities.
Issue-ID: MULTICLOUD-1324
Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>
Change-Id: I54ae12434572e2e2dd1fe2ec9298d04557331d94
Diffstat (limited to 'kud/deployment_infra/helm/ovn4nfv/templates')
11 files changed, 645 insertions, 0 deletions
diff --git a/kud/deployment_infra/helm/ovn4nfv/templates/_helpers.tpl b/kud/deployment_infra/helm/ovn4nfv/templates/_helpers.tpl new file mode 100644 index 00000000..4abd970e --- /dev/null +++ b/kud/deployment_infra/helm/ovn4nfv/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "ovn4nfv.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "ovn4nfv.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "ovn4nfv.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "ovn4nfv.labels" -}} +helm.sh/chart: {{ include "ovn4nfv.chart" . }} +{{ include "ovn4nfv.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "ovn4nfv.selectorLabels" -}} +app.kubernetes.io/name: {{ include "ovn4nfv.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "ovn4nfv.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "ovn4nfv.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/kud/deployment_infra/helm/ovn4nfv/templates/clusterrole.yaml b/kud/deployment_infra/helm/ovn4nfv/templates/clusterrole.yaml new file mode 100644 index 00000000..f10b111f --- /dev/null +++ b/kud/deployment_infra/helm/ovn4nfv/templates/clusterrole.yaml @@ -0,0 +1,54 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "ovn4nfv.fullname" . }} + labels: + {{- include "ovn4nfv.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - pods + - pods/status + - services + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + - nodes + verbs: + - '*' +- apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' +- apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - get + - create +- apiGroups: + - apps + resourceNames: + - nfn-operator + resources: + - deployments/finalizers + verbs: + - update +- apiGroups: + - k8s.plugin.opnfv.org + resources: + - '*' + - providernetworks + verbs: + - '*' +{{- end }} diff --git a/kud/deployment_infra/helm/ovn4nfv/templates/clusterrolebinding.yaml b/kud/deployment_infra/helm/ovn4nfv/templates/clusterrolebinding.yaml new file mode 100644 index 00000000..0891458a --- /dev/null +++ b/kud/deployment_infra/helm/ovn4nfv/templates/clusterrolebinding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create }} +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "ovn4nfv.fullname" . }} + labels: + {{- include "ovn4nfv.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "ovn4nfv.fullname" . }} +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:serviceaccounts +{{- end }} diff --git a/kud/deployment_infra/helm/ovn4nfv/templates/configmap.yaml b/kud/deployment_infra/helm/ovn4nfv/templates/configmap.yaml new file mode 100644 index 00000000..7e1beba1 --- /dev/null +++ b/kud/deployment_infra/helm/ovn4nfv/templates/configmap.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.nfnOperator.config.name }} +data: + {{ .Values.nfnOperator.config.data | nindent 2 }} +--- +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ .Values.cni.config.name }} + labels: + {{- include "ovn4nfv.labels" . | nindent 4 }} +data: + ovn4nfv_k8s.conf: | {{ .Values.cni.config.ovn4nfv_k8s | nindent 4 }} + 00-network.conf: | {{ .Values.cni.config.network | nindent 4 }} diff --git a/kud/deployment_infra/helm/ovn4nfv/templates/daemonset.yaml b/kud/deployment_infra/helm/ovn4nfv/templates/daemonset.yaml new file mode 100644 index 00000000..bc8285f4 --- /dev/null +++ b/kud/deployment_infra/helm/ovn4nfv/templates/daemonset.yaml @@ -0,0 +1,168 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ include "ovn4nfv.fullname" . }}-cni + labels: + {{- include "ovn4nfv.labels" . | nindent 4 }} + role: cni +spec: + updateStrategy: + type: RollingUpdate + selector: + matchLabels: + {{- include "ovn4nfv.selectorLabels" . | nindent 6 }} + role: cni + template: + metadata: + labels: + {{- include "ovn4nfv.selectorLabels" . | nindent 8 }} + role: cni + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + hostNetwork: true + {{- with .Values.cni.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.cni.tolerations }} + tolerations: + {{- toYaml . | nindent 6 }} + {{- end }} + serviceAccountName: {{ include "ovn4nfv.serviceAccountName" . }} + containers: + - name: ovn4nfv + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ["/usr/local/bin/entrypoint", "cni"] + lifecycle: + preStop: + exec: + command: ["/bin/bash", "-c", "rm /host/etc/cni/net.d/00-network.conf"] + resources: + {{- toYaml .Values.cni.resources | nindent 10 }} + securityContext: + {{- toYaml .Values.cni.securityContext | nindent 10 }} + volumeMounts: + - name: cni + mountPath: /host/etc/cni/net.d + - name: cnibin + mountPath: /host/opt/cni/bin + - name: cniconf + mountPath: /host/etc/openvswitch + - name: ovn4nfv-cfg + mountPath: /tmp/ovn4nfv-conf + - name: ovn4nfv-cni-net-conf + mountPath: /tmp/ovn4nfv-cni + volumes: + - name: cni + hostPath: + path: /etc/cni/net.d + - name: cnibin + hostPath: + path: /opt/cni/bin + - name: cniconf + hostPath: + path: /etc/openvswitch + - name: ovn4nfv-cfg + configMap: + name: {{ .Values.cni.config.name }} + items: + - key: ovn4nfv_k8s.conf + path: ovn4nfv_k8s.conf + - name: ovn4nfv-cni-net-conf + configMap: + name: {{ .Values.cni.config.name }} + items: + - key: 00-network.conf + path: 00-network.conf +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ include "ovn4nfv.fullname" . }}-nfn-agent + labels: + {{- include "ovn4nfv.labels" . | nindent 4 }} + role: nfn-agent +spec: + selector: + matchLabels: + {{- include "ovn4nfv.selectorLabels" . | nindent 6 }} + role: nfn-agent + updateStrategy: + type: RollingUpdate + template: + metadata: + labels: + {{- include "ovn4nfv.selectorLabels" . | nindent 8 }} + role: nfn-agent + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + hostNetwork: true + hostPID: true + {{- with .Values.nfnAgent.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nfnAgent.tolerations }} + tolerations: + {{- toYaml . | nindent 6 }} + {{- end }} + serviceAccountName: {{ include "ovn4nfv.serviceAccountName" . }} + containers: + - name: nfn-agent + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ["/usr/local/bin/entrypoint", "agent"] + resources: + {{- toYaml .Values.nfnAgent.resources | nindent 10 }} + env: + - name: NFN_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + securityContext: + {{- toYaml .Values.nfnAgent.securityContext | nindent 10 }} + volumeMounts: + - mountPath: /var/run/dbus/ + name: host-var-run-dbus + readOnly: true + - mountPath: /run/openvswitch + name: host-run-ovs + - mountPath: /var/run/openvswitch + name: host-var-run-ovs + - mountPath: /var/run + name: host-var-run + - mountPath: /host/proc + name: host-proc + - mountPath: /host/sys + name: host-sys + - mountPath: /var/run/ovn4nfv-k8s-plugin + name: host-var-cniserver-socket-dir + volumes: + - name: host-run-ovs + hostPath: + path: /run/openvswitch + - name: host-var-run-ovs + hostPath: + path: /var/run/openvswitch + - name: host-var-run-dbus + hostPath: + path: /var/run/dbus + - name: host-var-cniserver-socket-dir + hostPath: + path: /var/run/ovn4nfv-k8s-plugin + - name: host-var-run + hostPath: + path: /var/run + - name: host-proc + hostPath: + path: /proc + - name: host-sys + hostPath: + path: /sys diff --git a/kud/deployment_infra/helm/ovn4nfv/templates/deployment.yaml b/kud/deployment_infra/helm/ovn4nfv/templates/deployment.yaml new file mode 100644 index 00000000..7613fef5 --- /dev/null +++ b/kud/deployment_infra/helm/ovn4nfv/templates/deployment.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ovn4nfv.fullname" . }}-nfn-operator + labels: + {{- include "ovn4nfv.labels" . | nindent 4 }} + role: nfn-operator +spec: + replicas: 1 + selector: + matchLabels: + {{- include "ovn4nfv.selectorLabels" . | nindent 6 }} + role: nfn-operator + template: + metadata: + labels: + {{- include "ovn4nfv.selectorLabels" . | nindent 8 }} + role: nfn-operator + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + hostNetwork: true + {{- with .Values.nfnOperator.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nfnOperator.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nfnOperator.tolerations }} + tolerations: + {{- toYaml . | nindent 6 }} + {{- end }} + serviceAccountName: {{ include "ovn4nfv.serviceAccountName" . }} + containers: + - name: nfn-operator + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ["/usr/local/bin/entrypoint", "operator"] + envFrom: + - configMapRef: + name: {{ .Values.nfnOperator.config.name }} + ports: + - containerPort: 50000 + protocol: TCP + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: "nfn-operator" diff --git a/kud/deployment_infra/helm/ovn4nfv/templates/ovn/daemonset.yaml b/kud/deployment_infra/helm/ovn4nfv/templates/ovn/daemonset.yaml new file mode 100644 index 00000000..2b71a9dd --- /dev/null +++ b/kud/deployment_infra/helm/ovn4nfv/templates/ovn/daemonset.yaml @@ -0,0 +1,102 @@ +kind: DaemonSet +apiVersion: apps/v1 +metadata: + name: {{ include "ovn4nfv.fullname" . }}-ovn-controller + labels: + {{- include "ovn4nfv.labels" . | nindent 4 }} + role: ovn-controller +spec: + selector: + matchLabels: + {{- include "ovn4nfv.selectorLabels" . | nindent 6 }} + role: ovn-controller + updateStrategy: + type: OnDelete + template: + metadata: + labels: + {{- include "ovn4nfv.selectorLabels" . | nindent 8 }} + role: ovn-controller + spec: + {{- with .Values.ovn.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + hostNetwork: true + hostPID: true + {{- with .Values.ovnController.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ovnController.tolerations }} + tolerations: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.ovnController.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + priorityClassName: system-cluster-critical + containers: + - name: ovn-controller + image: "{{ .Values.ovn.image.repository }}:{{ .Values.ovn.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.ovn.image.pullPolicy }} + command: ["ovn4nfv-k8s", "start_ovn_controller"] + resources: + {{- toYaml .Values.ovnController.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.ovnController.securityContext | nindent 12 }} + env: + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + volumeMounts: + - mountPath: /lib/modules + name: host-modules + readOnly: true + - mountPath: /var/run/openvswitch + name: host-run-ovs + - mountPath: /var/run/ovn + name: host-run-ovn + - mountPath: /sys + name: host-sys + readOnly: true + - mountPath: /etc/openvswitch + name: host-config-openvswitch + - mountPath: /var/log/openvswitch + name: host-log-ovs + - mountPath: /var/log/ovn + name: host-log-ovn + readinessProbe: + exec: + command: ["ovn4nfv-k8s", "check_ovn_controller"] + periodSeconds: 5 + livenessProbe: + exec: + command: ["ovn4nfv-k8s", "check_ovn_controller"] + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 5 + volumes: + - name: host-modules + hostPath: + path: /lib/modules + - name: host-run-ovs + hostPath: + path: /run/openvswitch + - name: host-run-ovn + hostPath: + path: /run/ovn + - name: host-sys + hostPath: + path: /sys + - name: host-config-openvswitch + hostPath: + path: /etc/origin/openvswitch + - name: host-log-ovs + hostPath: + path: /var/log/openvswitch + - name: host-log-ovn + hostPath: + path: /var/log/ovn diff --git a/kud/deployment_infra/helm/ovn4nfv/templates/ovn/deployment.yaml b/kud/deployment_infra/helm/ovn4nfv/templates/ovn/deployment.yaml new file mode 100644 index 00000000..a9dd4288 --- /dev/null +++ b/kud/deployment_infra/helm/ovn4nfv/templates/ovn/deployment.yaml @@ -0,0 +1,107 @@ +kind: Deployment +apiVersion: apps/v1 +metadata: + name: {{ include "ovn4nfv.fullname" . }}-ovn-control-plane + labels: + {{- include "ovn4nfv.labels" . | nindent 4 }} + role: ovn-control-plane +spec: + replicas: 1 + strategy: + rollingUpdate: + maxSurge: 0% + maxUnavailable: 100% + type: RollingUpdate + selector: + matchLabels: + {{- include "ovn4nfv.selectorLabels" . | nindent 6 }} + role: ovn-control-plane + template: + metadata: + labels: + {{- include "ovn4nfv.selectorLabels" . | nindent 8 }} + role: ovn-control-plane + spec: + {{- with .Values.ovn.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + hostNetwork: true + {{- with .Values.ovnControlPlane.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ovnControlPlane.tolerations }} + tolerations: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.ovnControlPlane.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + priorityClassName: system-cluster-critical + containers: + - name: ovn-control-plane + image: "{{ .Values.ovn.image.repository }}:{{ .Values.ovn.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.ovn.image.pullPolicy }} + command: ["ovn4nfv-k8s", "start_ovn_control_plane"] + resources: + {{- toYaml .Values.ovnControlPlane.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.ovnControlPlane.securityContext | nindent 12 }} + env: + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - mountPath: /var/run/openvswitch + name: host-run-ovs + - mountPath: /var/run/ovn + name: host-run-ovn + - mountPath: /sys + name: host-sys + readOnly: true + - mountPath: /etc/openvswitch + name: host-config-openvswitch + - mountPath: /var/log/openvswitch + name: host-log-ovs + - mountPath: /var/log/ovn + name: host-log-ovn + readinessProbe: + exec: + command: ["ovn4nfv-k8s", "check_ovn_control_plane"] + periodSeconds: 3 + livenessProbe: + exec: + command: ["ovn4nfv-k8s", "check_ovn_control_plane"] + initialDelaySeconds: 30 + periodSeconds: 7 + failureThreshold: 5 + volumes: + - name: host-run-ovs + hostPath: + path: /run/openvswitch + - name: host-run-ovn + hostPath: + path: /run/ovn + - name: host-sys + hostPath: + path: /sys + - name: host-config-openvswitch + hostPath: + path: /etc/origin/openvswitch + - name: host-log-ovs + hostPath: + path: /var/log/openvswitch + - name: host-log-ovn + hostPath: + path: /var/log/ovn diff --git a/kud/deployment_infra/helm/ovn4nfv/templates/ovn/service.yaml b/kud/deployment_infra/helm/ovn4nfv/templates/ovn/service.yaml new file mode 100644 index 00000000..c6d96e49 --- /dev/null +++ b/kud/deployment_infra/helm/ovn4nfv/templates/ovn/service.yaml @@ -0,0 +1,37 @@ +kind: Service +apiVersion: v1 +metadata: + name: ovn-nb-tcp + labels: + {{- include "ovn4nfv.labels" . | nindent 4 }} + role: ovn-control-plane +spec: + ports: + - name: ovn-nb-tcp + protocol: TCP + port: {{ .Values.ovnControlPlane.nbService.port }} + targetPort: 6641 + type: {{ .Values.ovnControlPlane.nbService.type }} + selector: + {{- include "ovn4nfv.selectorLabels" . | nindent 4 }} + role: ovn-control-plane + sessionAffinity: None +--- +kind: Service +apiVersion: v1 +metadata: + name: ovn-sb-tcp + labels: + {{- include "ovn4nfv.labels" . | nindent 4 }} + role: ovn-control-plane +spec: + ports: + - name: ovn-sb-tcp + protocol: TCP + port: {{ .Values.ovnControlPlane.sbService.port }} + targetPort: 6642 + type: {{ .Values.ovnControlPlane.sbService.type }} + selector: + {{- include "ovn4nfv.selectorLabels" . | nindent 4 }} + role: ovn-control-plane + sessionAffinity: None diff --git a/kud/deployment_infra/helm/ovn4nfv/templates/service.yaml b/kud/deployment_infra/helm/ovn4nfv/templates/service.yaml new file mode 100644 index 00000000..a9e5747d --- /dev/null +++ b/kud/deployment_infra/helm/ovn4nfv/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: nfn-operator + labels: + {{- include "ovn4nfv.labels" . | nindent 4 }} + role: nfn-operator +spec: + type: {{ .Values.nfnOperator.service.type }} + ports: + - port: {{ .Values.nfnOperator.service.port }} + protocol: TCP + targetPort: 50000 + selector: + {{- include "ovn4nfv.selectorLabels" . | nindent 4 }} + role: nfn-operator diff --git a/kud/deployment_infra/helm/ovn4nfv/templates/serviceaccount.yaml b/kud/deployment_infra/helm/ovn4nfv/templates/serviceaccount.yaml new file mode 100644 index 00000000..853e2ca1 --- /dev/null +++ b/kud/deployment_infra/helm/ovn4nfv/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "ovn4nfv.serviceAccountName" . }} + labels: + {{- include "ovn4nfv.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end -}} |