---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ template "elasticsearch.uname" . }}
  labels:
    heritage: {{ .Release.Service | quote }}
    release: {{ .Release.Name | quote }}
    chart: "{{ .Chart.Name }}"
    app: "{{ template "elasticsearch.uname" . }}"
    {{- range $key, $value := .Values.labels }}
    {{ $key }}: {{ $value | quote }}
    {{- end }}
  annotations:
    esMajorVersion: "{{ include "elasticsearch.esMajorVersion" . }}"
spec:
  serviceName: {{ template "elasticsearch.uname" . }}-headless
  selector:
    matchLabels:
      app: "{{ template "elasticsearch.uname" . }}"
  replicas: {{ .Values.replicas }}
  podManagementPolicy: {{ .Values.podManagementPolicy }}
  updateStrategy:
    type: {{ .Values.updateStrategy }}
  {{- if .Values.persistence.enabled }}
  volumeClaimTemplates:
  - metadata:
      name: {{ template "elasticsearch.uname" . }}
    {{- if .Values.persistence.labels.enabled }}
      labels:
        release: {{ .Release.Name | quote }}
        chart: "{{ .Chart.Name }}"
        app: "{{ template "elasticsearch.uname" . }}"
        {{- range $key, $value := .Values.labels }}
        {{ $key }}: {{ $value | quote }}
        {{- end }}
    {{- end }}
    {{- with .Values.persistence.annotations  }}
      annotations:
{{ toYaml . | indent 8 }}
    {{- end }}
    spec:
{{ toYaml .Values.volumeClaimTemplate | indent 6 }}
  {{- end }}
  template:
    metadata:
      name: "{{ template "elasticsearch.uname" . }}"
      labels:
        release: {{ .Release.Name | quote }}
        chart: "{{ .Chart.Name }}"
        app: "{{ template "elasticsearch.uname" . }}"
        {{- range $key, $value := .Values.labels }}
        {{ $key }}: {{ $value | quote }}
        {{- end }}
      annotations:
        {{- range $key, $value := .Values.podAnnotations }}
        {{ $key }}: {{ $value | quote }}
        {{- end }}
        {{/* This forces a restart if the configmap has changed */}}
        {{- if or .Values.esConfig .Values.esJvmOptions }}
        configchecksum: {{ include (print .Template.BasePath "/configmap.yaml") . | sha256sum | trunc 63 }}
        {{- end }}
    spec:
      {{- if .Values.schedulerName }}
      schedulerName: "{{ .Values.schedulerName }}"
      {{- end }}
      securityContext:
{{ toYaml .Values.podSecurityContext | indent 8 }}
        {{- if .Values.fsGroup }}
        fsGroup: {{ .Values.fsGroup }} # Deprecated value, please use .Values.podSecurityContext.fsGroup
        {{- end }}
      {{- if or .Values.rbac.create .Values.rbac.serviceAccountName }}
      serviceAccountName: "{{ template "elasticsearch.serviceAccount" . }}"
      {{- end }}
      automountServiceAccountToken: {{ .Values.rbac.automountToken }}
      {{- with .Values.tolerations }}
      tolerations:
{{ toYaml . | indent 6 }}
      {{- end }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
{{ toYaml . | indent 8 }}
      {{- end }}
      {{- if or (eq .Values.antiAffinity "hard") (eq .Values.antiAffinity "soft") .Values.nodeAffinity }}
      {{- if .Values.priorityClassName }}
      priorityClassName: {{ .Values.priorityClassName }}
      {{- end }}
      affinity:
      {{- end }}
      {{- if eq .Values.antiAffinity "hard" }}
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - "{{ template "elasticsearch.uname" .}}"
            topologyKey: {{ .Values.antiAffinityTopologyKey }}
      {{- else if eq .Values.antiAffinity "soft" }}
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 1
            podAffinityTerm:
              topologyKey: {{ .Values.antiAffinityTopologyKey }}
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - "{{ template "elasticsearch.uname" . }}"
      {{- end }}
      {{- with .Values.nodeAffinity }}
        nodeAffinity:
{{ toYaml . | indent 10 }}
      {{- end }}
      terminationGracePeriodSeconds: {{ .Values.terminationGracePeriod }}
      volumes:
        {{- range .Values.secretMounts }}
        - name: {{ .name }}
          secret:
            secretName: {{ .secretName }}
            {{- if .defaultMode }}
            defaultMode: {{ .defaultMode }}
            {{- end }}
        {{- end }}
        {{- if .Values.esConfig }}
        - name: esconfig
          configMap:
            name: {{ template "elasticsearch.uname" . }}-config
        {{- end }}
        {{- if .Values.esJvmOptions }}
        - name: esjvmoptions
          configMap:
            name: {{ template "elasticsearch.uname" . }}-jvm-options
        {{- end }}
        {{- if .Values.createCert }}
        - name: elasticsearch-certs
          secret:
           secretName: {{ template "elasticsearch.uname" . }}-certs
        {{- end }}
{{- if .Values.keystore }}
        - name: keystore
          emptyDir: {}
        {{- range .Values.keystore }}
        - name: keystore-{{ .secretName }}
          secret: {{ toYaml . | nindent 12 }}
        {{- end }}
{{ end }}
      {{- if .Values.extraVolumes }}
      # Currently some extra blocks accept strings
      # to continue with backwards compatibility this is being kept
      # whilst also allowing for yaml to be specified too.
      {{- if eq "string" (printf "%T" .Values.extraVolumes) }}
{{ tpl .Values.extraVolumes . | indent 8 }}
      {{- else }}
{{ toYaml .Values.extraVolumes | indent 8 }}
      {{- end }}
      {{- end }}
      {{- if .Values.imagePullSecrets }}
      imagePullSecrets:
{{ toYaml .Values.imagePullSecrets | indent 8 }}
      {{- end }}
      enableServiceLinks: {{ .Values.enableServiceLinks }}
      {{- if .Values.hostAliases }}
      hostAliases: {{ toYaml .Values.hostAliases | nindent 8 }}
      {{- end }}
      {{- if or (.Values.extraInitContainers) (.Values.sysctlInitContainer.enabled) (.Values.keystore)  }}
      initContainers:
      {{- if .Values.sysctlInitContainer.enabled }}
      - name: configure-sysctl
        securityContext:
          runAsUser: 0
          privileged: true
        image: "{{ .Values.image }}:{{ .Values.imageTag }}"
        imagePullPolicy: "{{ .Values.imagePullPolicy }}"
        command: ["sysctl", "-w", "vm.max_map_count={{ .Values.sysctlVmMaxMapCount}}"]
        resources:
{{ toYaml .Values.initResources | indent 10 }}
      {{- end }}
{{ if .Values.keystore }}
      - name: keystore
        securityContext:
{{ toYaml .Values.securityContext | indent 10 }}
        image: "{{ .Values.image }}:{{ .Values.imageTag }}"
        imagePullPolicy: "{{ .Values.imagePullPolicy }}"
        command:
        - bash
        - -c
        - |
          set -euo pipefail

          elasticsearch-keystore create

          for i in /tmp/keystoreSecrets/*/*; do
            key=$(basename $i)
            echo "Adding file $i to keystore key $key"
            elasticsearch-keystore add-file "$key" "$i"
          done

          # Add the bootstrap password since otherwise the Elasticsearch entrypoint tries to do this on startup
          if [ ! -z ${ELASTIC_PASSWORD+x} ]; then
            echo 'Adding env $ELASTIC_PASSWORD to keystore as key bootstrap.password'
            echo "$ELASTIC_PASSWORD" | elasticsearch-keystore add -x bootstrap.password
          fi

          cp -a /usr/share/elasticsearch/config/elasticsearch.keystore /tmp/keystore/
        env: {{ toYaml .Values.extraEnvs | nindent 10 }}
        envFrom: {{ toYaml .Values.envFrom | nindent 10 }}
        resources: {{ toYaml .Values.initResources | nindent 10 }}
        volumeMounts:
          - name: keystore
            mountPath: /tmp/keystore
          {{- range .Values.keystore }}
          - name: keystore-{{ .secretName }}
            mountPath: /tmp/keystoreSecrets/{{ .secretName }}
          {{- end }}
{{ end }}
      {{- if .Values.extraInitContainers }}
      # Currently some extra blocks accept strings
      # to continue with backwards compatibility this is being kept
      # whilst also allowing for yaml to be specified too.
      {{- if eq "string" (printf "%T" .Values.extraInitContainers) }}
{{ tpl .Values.extraInitContainers . | indent 6 }}
      {{- else }}
{{ toYaml .Values.extraInitContainers | indent 6 }}
      {{- end }}
      {{- end }}
      {{- end }}
      containers:
      - name: "{{ template "elasticsearch.name" . }}"
        securityContext:
{{ toYaml .Values.securityContext | indent 10 }}
        image: "{{ .Values.image }}:{{ .Values.imageTag }}"
        imagePullPolicy: "{{ .Values.imagePullPolicy }}"
        readinessProbe:
          exec:
            command:
              - bash
              - -c
              - |
                set -e

                # Exit if ELASTIC_PASSWORD in unset
                if [ -z "${ELASTIC_PASSWORD}" ]; then
                  echo "ELASTIC_PASSWORD variable is missing, exiting"
                  exit 1
                fi

                # If the node is starting up wait for the cluster to be ready (request params: "{{ .Values.clusterHealthCheckParams }}" )
                # Once it has started only check that the node itself is responding
                START_FILE=/tmp/.es_start_file

                # Disable nss cache to avoid filling dentry cache when calling curl
                # This is required with Elasticsearch Docker using nss < 3.52
                export NSS_SDB_USE_CACHE=no

                http () {
                  local path="${1}"
                  local args="${2}"
                  set -- -XGET -s

                  if [ "$args" != "" ]; then
                    set -- "$@" $args
                  fi

                  set -- "$@" -u "elastic:${ELASTIC_PASSWORD}"

                  curl --output /dev/null -k "$@" "{{ .Values.protocol }}://127.0.0.1:{{ .Values.httpPort }}${path}"
                }

                if [ -f "${START_FILE}" ]; then
                  echo 'Elasticsearch is already running, lets check the node is healthy'
                  HTTP_CODE=$(http "/" "-w %{http_code}")
                  RC=$?
                  if [[ ${RC} -ne 0 ]]; then
                    echo "curl --output /dev/null -k -XGET -s -w '%{http_code}' \${BASIC_AUTH} {{ .Values.protocol }}://127.0.0.1:{{ .Values.httpPort }}/ failed with RC ${RC}"
                    exit ${RC}
                  fi
                  # ready if HTTP code 200, 503 is tolerable if ES version is 6.x
                  if [[ ${HTTP_CODE} == "200" ]]; then
                    exit 0
                  elif [[ ${HTTP_CODE} == "503" && "{{ include "elasticsearch.esMajorVersion" . }}" == "6" ]]; then
                    exit 0
                  else
                    echo "curl --output /dev/null -k -XGET -s -w '%{http_code}' \${BASIC_AUTH} {{ .Values.protocol }}://127.0.0.1:{{ .Values.httpPort }}/ failed with HTTP code ${HTTP_CODE}"
                    exit 1
                  fi

                else
                  echo 'Waiting for elasticsearch cluster to become ready (request params: "{{ .Values.clusterHealthCheckParams }}" )'
                  if http "/_cluster/health?{{ .Values.clusterHealthCheckParams }}" "--fail" ; then
                    touch ${START_FILE}
                    exit 0
                  else
                    echo 'Cluster is not yet ready (request params: "{{ .Values.clusterHealthCheckParams }}" )'
                    exit 1
                  fi
                fi
{{ toYaml .Values.readinessProbe | indent 10 }}
        ports:
        - name: http
          containerPort: {{ .Values.httpPort }}
        - name: transport
          containerPort: {{ .Values.transportPort }}
        resources:
{{ toYaml .Values.resources | indent 10 }}
        env:
          - name: node.name
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          {{- if has "master" .Values.roles }}
          - name: cluster.initial_master_nodes
            value: "{{ template "elasticsearch.endpoints" . }}"
          {{- end }}
          {{- if gt (len (include "elasticsearch.roles" .)) 0 }}
          - name: node.roles
            value: "{{ template "elasticsearch.roles" . }}"
          {{- end }}
          {{- if lt (int (include "elasticsearch.esMajorVersion" .)) 7 }}
          - name: discovery.zen.ping.unicast.hosts
            value: "{{ template "elasticsearch.masterService" . }}-headless"
          {{- else }}
          - name: discovery.seed_hosts
            value: "{{ template "elasticsearch.masterService" . }}-headless"
          {{- end }}
          - name: cluster.name
            value: "{{ .Values.clusterName }}"
          - name: network.host
            value: "{{ .Values.networkHost }}"
          {{- if .Values.secret.enabled }}
          - name: ELASTIC_PASSWORD
            valueFrom:
              secretKeyRef:
                name: {{ template "elasticsearch.uname" . }}-credentials
                key: password
          {{- end }}
          {{- if .Values.esJavaOpts  }}
          - name: ES_JAVA_OPTS
            value: "{{ .Values.esJavaOpts }}"
          {{- end }}
          {{- if .Values.createCert }}
          - name: xpack.security.enabled
            value: "true"
          - name: xpack.security.transport.ssl.enabled
            value: "true"
          - name: xpack.security.http.ssl.enabled
            value: "true"
          - name: xpack.security.transport.ssl.verification_mode
            value: "certificate"
          - name: xpack.security.transport.ssl.key
            value: "/usr/share/elasticsearch/config/certs/tls.key"
          - name: xpack.security.transport.ssl.certificate
            value: "/usr/share/elasticsearch/config/certs/tls.crt"
          - name: xpack.security.transport.ssl.certificate_authorities
            value: "/usr/share/elasticsearch/config/certs/ca.crt"
          - name: xpack.security.http.ssl.key
            value: "/usr/share/elasticsearch/config/certs/tls.key"
          - name: xpack.security.http.ssl.certificate
            value: "/usr/share/elasticsearch/config/certs/tls.crt"
          - name: xpack.security.http.ssl.certificate_authorities
            value: "/usr/share/elasticsearch/config/certs/ca.crt"
          {{- end }}
{{- if .Values.extraEnvs }}
{{ toYaml .Values.extraEnvs | indent 10 }}
{{- end }}
{{- if .Values.envFrom }}
        envFrom:
{{ toYaml .Values.envFrom | indent 10 }}
{{- end }}
        volumeMounts:
          {{- if .Values.persistence.enabled }}
          - name: "{{ template "elasticsearch.uname" . }}"
            mountPath: /usr/share/elasticsearch/data
          {{- end }}
          {{- if .Values.createCert }}
          - name: elasticsearch-certs
            mountPath: /usr/share/elasticsearch/config/certs
            readOnly: true
          {{- end }}
{{ if .Values.keystore }}
          - name: keystore
            mountPath: /usr/share/elasticsearch/config/elasticsearch.keystore
            subPath: elasticsearch.keystore
{{ end }}
          {{- range .Values.secretMounts }}
          - name: {{ .name }}
            mountPath: {{ .path }}
            {{- if .subPath }}
            subPath: {{ .subPath }}
            {{- end }}
          {{- end }}
          {{- range $path, $config := .Values.esConfig }}
          - name: esconfig
            mountPath: /usr/share/elasticsearch/config/{{ $path }}
            subPath: {{ $path }}
          {{- end -}}
          {{- range $path, $config := .Values.esJvmOptions }}
          - name: esjvmoptions
            mountPath: /usr/share/elasticsearch/config/jvm.options.d/{{ $path }}
            subPath: {{ $path }}
          {{- end -}}
        {{- if .Values.extraVolumeMounts }}
        # Currently some extra blocks accept strings
        # to continue with backwards compatibility this is being kept
        # whilst also allowing for yaml to be specified too.
        {{- if eq "string" (printf "%T" .Values.extraVolumeMounts) }}
{{ tpl .Values.extraVolumeMounts . | indent 10 }}
        {{- else }}
{{ toYaml .Values.extraVolumeMounts | indent 10 }}
        {{- end }}
        {{- end }}
{{- if .Values.lifecycle }}
        lifecycle:
{{ toYaml .Values.lifecycle | indent 10 }}
{{- end }}
      {{- if .Values.extraContainers }}
      # Currently some extra blocks accept strings
      # to continue with backwards compatibility this is being kept
      # whilst also allowing for yaml to be specified too.
      {{- if eq "string" (printf "%T" .Values.extraContainers) }}
{{ tpl .Values.extraContainers . | indent 6 }}
      {{- else }}
{{ toYaml .Values.extraContainers | indent 6 }}
      {{- end }}
      {{- end }}
