# helm_charts_consul-statefulset.yaml
apiVersion: {{ template "statefulset.apiVersion" . }}
kind: StatefulSet
metadata:
  name: "{{ template "consul.fullname" . }}"
  labels:
    heritage: {{ .Release.Service | quote }}
    release: {{ .Release.Name | quote }}
    chart: {{ template "consul.chart" . }}
    component: "{{ .Release.Name }}-{{ .Values.Component }}"
{{- if .Values.additionalLabels }}
{{ toYaml .Values.additionalLabels | indent 4 }}
{{- end }}
spec:
  serviceName: "{{ template "consul.fullname" . }}"
  replicas: {{ default 3 .Values.Replicas }}
  updateStrategy:
    type: RollingUpdate
  selector:
    matchLabels:
      release: {{ .Release.Name | quote }}
      component: "{{ .Release.Name }}-{{ .Values.Component }}"
  template:
    metadata:
      name: "{{ template "consul.fullname" . }}"
{{- if .Values.podAnnotations }}
      annotations:
{{ toYaml .Values.podAnnotations | indent 8 }}
{{- end }}
      labels:
        heritage: {{ .Release.Service | quote }}
        release: {{ .Release.Name | quote }}
        chart: {{ template "consul.chart" . }}
        component: "{{ .Release.Name }}-{{ .Values.Component }}"
{{- if .Values.additionalLabels }}
{{ toYaml .Values.additionalLabels | indent 8 }}
{{- end }}
    spec:
      securityContext:
        fsGroup: 1000
{{- if .Values.priorityClassName }}
      priorityClassName: "{{ .Values.priorityClassName }}"
{{- end }}
    {{- if .Values.affinity }}
      affinity:
{{ tpl .Values.affinity . | indent 8 }}
    {{- end }}
    {{- if .Values.nodeSelector }}
      nodeSelector:
{{ toYaml .Values.nodeSelector | indent 8 }}
    {{- end }}
    {{- if .Values.tolerations }}
      tolerations:
{{ toYaml .Values.tolerations | indent 8 }}
    {{- end }}
      containers:
      - name: "{{ template "consul.fullname" . }}"
        image: "{{ .Values.Image }}:{{ .Values.ImageTag }}"
        imagePullPolicy: "{{ .Values.ImagePullPolicy }}"
        ports:
        - name: http
          containerPort: {{ .Values.HttpPort }}
        - name: rpc
          containerPort: {{ .Values.RpcPort }}
        - name: serflan-tcp
          protocol: "TCP"
          containerPort: {{ .Values.SerflanPort }}
        - name: serflan-udp
          protocol: "UDP"
          containerPort: {{ .Values.SerflanUdpPort }}
        - name: serfwan-tcp
          protocol: "TCP"
          containerPort: {{ .Values.SerfwanPort }}
        - name: serfwan-udp
          protocol: "UDP"
          containerPort: {{ .Values.SerfwanUdpPort }}
        - name: server
          containerPort: {{.Values.ServerPort}}
        - name: consuldns-tcp
          containerPort: {{.Values.ConsulDnsPort}}
        - name: consuldns-udp
          protocol: "UDP"
          containerPort: {{.Values.ConsulDnsPort}}
        resources:
{{ toYaml .Values.Resources | indent 10 }}
        env:
        - name: INITIAL_CLUSTER_SIZE
          value: {{ default 3 .Values.Replicas | quote }}
        - name: STATEFULSET_NAME
          value: "{{ template "consul.fullname" . }}"
        - name: POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: STATEFULSET_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: DNSPORT
          value: "{{ .Values.ConsulDnsPort }}"
        volumeMounts:
        - name: datadir
          mountPath: /var/lib/consul
        - name: gossip-key
          mountPath: /etc/consul/secrets
          readOnly: true
        {{ range .Values.ConsulConfig }}
        - name: userconfig-{{ .name }}
          readOnly: true
          mountPath: /etc/consul/userconfig/{{ .name }}
        {{ end }}
        {{- if .Values.lifecycle }}
        lifecycle:
{{ tpl (toYaml .Values.lifecycle) . | indent 10 }}
        {{- end }}
        livenessProbe:
          exec:
            command:
            - consul
            - members
            - -http-addr=http://127.0.0.1:{{ .Values.HttpPort }}
          initialDelaySeconds: 300
          timeoutSeconds: 5
        command:
          - "/bin/sh"
          - "-ec"
          - |
            set -o pipefail

            if [ -z "$POD_IP"  ]; then
              POD_IP=$(hostname -i)
            fi
            FQDN_SUFFIX="${STATEFULSET_NAME}.${STATEFULSET_NAMESPACE}.svc{{- if .Values.ClusterDomain }}.{{ .Values.ClusterDomain }}{{- end -}}"
            NODE_NAME="$(hostname -s).${FQDN_SUFFIX}"
            {{- if .Values.Gossip.Encrypt }}
            if [ -e /etc/consul/secrets/gossip-key ]; then
              echo "{\"encrypt\": \"$(base64 /etc/consul/secrets/gossip-key)\"}" > /etc/consul/encrypt.json
              GOSSIP_KEY="-config-file /etc/consul/encrypt.json"
            fi
            {{- end }}

            JOIN_PEERS=""

            {{- if .Values.joinPeers }}
            {{- range .Values.joinPeers }}
            JOIN_PEERS="${JOIN_PEERS}${JOIN_PEERS:+ }{{ . }}"
            {{- end }}
            {{- else }}
            JOIN_PEERS=""
            for i in $( seq 0 $((${INITIAL_CLUSTER_SIZE} - 1)) ); do
              JOIN_PEERS="${JOIN_PEERS}${JOIN_PEERS:+ }${STATEFULSET_NAME}-${i}.${FQDN_SUFFIX}"
            done
            {{- end }}

            JOIN_PEERS=$( printf "%s\n" $JOIN_PEERS | sort | uniq )

            # Require multiple loops in the case of unstable DNS resolution
            SUCCESS_LOOPS=5
            while [ "$SUCCESS_LOOPS" -gt 0 ]; do
              ALL_READY=true
              JOIN_LAN=""
              for THIS_PEER in $JOIN_PEERS; do
                  # Make sure we can resolve hostname and ping IP
                  if PEER_IP="$(ping -c 1 $THIS_PEER | awk -F'[()]' '/PING/{print $2}')" && [ "$PEER_IP" != "" ]; then
                    if [ "${PEER_IP}" != "${POD_IP}" ]; then
                      JOIN_LAN="${JOIN_LAN}${JOIN_LAN:+ } -retry-join=$THIS_PEER"
                    fi
                  else
                    ALL_READY=false
                    break
                  fi
              done
              if $ALL_READY; then
                SUCCESS_LOOPS=$(( SUCCESS_LOOPS - 1 ))
                echo "LAN peers appear ready, $SUCCESS_LOOPS verifications left"
              else
                echo "Waiting for LAN peer $THIS_PEER..."
              fi
              sleep 1s
            done


            WAN_PEERS=""
            {{- range .Values.joinWan }}
            WAN_PEERS="${WAN_PEERS}${WAN_PEERS:+ }{{ . }}"
            {{- end }}

            JOIN_WAN=""
            SUCCESS_LOOPS=5
            while [ "$WAN_PEERS" != "" ] && [ "$SUCCESS_LOOPS" -gt 0 ]; do
              ALL_READY=true
              JOIN_WAN=""
              for THIS_PEER in $WAN_PEERS; do
                  # We don't care if we can ping the peer, but we do care that we can get its IP
                  if PEER_IP="$( ( ping -c 1 $THIS_PEER || true ) | awk -F'[()]' '/PING/{print $2}')" && [ "$PEER_IP" != "" ]; then
                    if [ "${PEER_IP}" != "${POD_IP}" ]; then
                      JOIN_WAN="${JOIN_WAN}${JOIN_WAN:+ } -retry-join-wan=$THIS_PEER"
                    fi
                  else
                    ALL_READY=false
                    break
                  fi
              done
              if $ALL_READY; then
                SUCCESS_LOOPS=$(( SUCCESS_LOOPS - 1 ))
                echo "WAN peers appear ready, $SUCCESS_LOOPS verifications left"
              else
                echo "Waiting for WAN peer $THIS_PEER..."
              fi
              sleep 1s
            done

            exec /bin/consul agent \
            {{- range .Values.ConsulConfig }}
              -config-dir /etc/consul/userconfig/{{ .name }} \
            {{- end}}
            {{- if .Values.ui.enabled }}
              -ui \
            {{- end }}
            {{- if .Values.DisableHostNodeId }}
              -disable-host-node-id \
            {{- end }}
            {{- if .Values.DatacenterName }}
              -datacenter {{ .Values.DatacenterName }} \
            {{- end }}
            {{- if .Values.Domain }}
              -domain={{ .Values.Domain }} \
            {{- end }}
              -data-dir=/var/lib/consul \
              -server \
              -bootstrap-expect=$( echo "$JOIN_PEERS" | wc -w ) \
              -disable-keyring-file \
            {{- if .Values.forceIpv6 }}
              -bind=:: \
            {{- else }}
              -bind=0.0.0.0 \
            {{- end }}
              -advertise=${POD_IP} \
              ${JOIN_LAN} \
              ${JOIN_WAN} \
            {{- if .Values.Gossip.Encrypt }}
              ${GOSSIP_KEY} \
            {{- end }}
            {{- if .Values.forceIpv6 }}
              -client=:: \
            {{- else }}
              -client=0.0.0.0 \
            {{- end }}
              -dns-port=${DNSPORT} \
              -http-port={{ .Values.HttpPort }}
      volumes:
      - name: gossip-key
        secret:
          secretName: {{ template "consul.fullname" . }}-gossip-key
      {{ range .Values.ConsulConfig }}
      - name: userconfig-{{ .name }}
        {{ .type }}:
          {{- if (eq .type "configMap") }}
          name: {{ .name }}
          {{- else if (eq .type "secret") }}
          secretName: {{ .name }}
          {{- end}}
      {{ end }}
  volumeClaimTemplates:
  - metadata:
      name: datadir
    spec:
      accessModes:
        - "ReadWriteOnce"
      resources:
        requests:
          # upstream recommended max is 700M
          storage: "{{ .Values.Storage }}"
    {{- if .Values.StorageClass }}
    {{- if (eq "-" .Values.StorageClass) }}
      storageClassName: ""
    {{- else }}
      storageClassName: "{{ .Values.StorageClass }}"
    {{- end }}
    {{- end }}