apiVersion: v1
kind: ConfigMap
metadata:
  name: dandelion-haproxy-configmap
data:
  topology-disabled: |
          { "name": "TICKR",        "addr": "localhost", "port": 443, "protocol": "https" },
  topology-mainnet.json: |  
    {
      "Providers": [
          { "name": "PEACE-us",     "addr": "contabo.us.dandelion.link", "port": 443, "protocol": "https" },
          { "name": "PEACE-es",     "addr": "soltia.es.dandelion.link", "port": 443, "protocol": "https" },
          { "name": "PEACE-fr",     "addr": "v11.mainnet.gc.scw.dandelion.link", "port": 443, "protocol": "https" },
          { "name": "EASY1",        "addr": "gio.dandelion.link", "port": 443, "protocol": "https" },
          { "name": "RSV",          "addr": "dmn.reservoir.network", "port": 443, "protocol": "https" },
          { "name": "BOOST",        "addr": "baremetal.cardano.dbooster.io", "port": 443, "protocol": "https" },
          { "name": "FLUID7",       "addr": "dandelion.fluid7.com", "port": 443, "protocol": "https" },
          { "name": "saldrich",     "addr": "dandelionflorida.ddns.net", "port": 443, "protocol": "https" }
      ],
      "Consumers": []
    }

  topology-testnet.json: |
    {
      "Providers": [
          { "name": "PEACE-us",     "addr": "contabo.us.dandelion.link", "port": 443, "protocol": "https" },
          { "name": "PEACE-es", "addr": "soltia.es.dandelion.link", "port": 443, "protocol": "https" },
          { "name": "PEACE-fr",     "addr": "v11.testnet.gc.scw.dandelion.link", "port": 443, "protocol": "https" },
          { "name": "EASY1",        "addr": "gio.dandelion.link", "port": 443, "protocol": "https" },
          { "name": "RSV",          "addr": "dtn.reservoir.network", "port": 443, "protocol": "https" },
          { "name": "BOOST",        "addr": "baremetal.cardano.dbooster.io", "port": 443, "protocol": "https" },
          { "name": "FLUID7",       "addr": "dandelion.fluid7.com", "port": 443, "protocol": "https" },
          { "name": "saldrich",     "addr": "dandelionflorida.ddns.net", "port": 443, "protocol": "https" }
      ],
      "Consumers": []
    }

  disabled-graphql-api-healthcheck.tpl: |
    option httpchk
    http-check send meth GET uri / ver HTTP/1.1 hdr host ${INGRESS_DOMAIN}
    http-check expect rstring GET.query.missing
  graphql-api-healthcheck.tpl: |
    option external-check
    external-check path "/usr/bin:/bin:/tmp:/sbin:/usr/sbin"
    external-check command /configmap/graphql-api-hc.sh
  submit-api-healthcheck.tpl: |
    option httpchk
    http-check send meth POST uri /api/submit/tx ver HTTP/1.1 hdr host ${INGRESS_DOMAIN}
    http-check expect status 415
  explorer-api-healthcheck.tpl: |
    option httpchk
    http-check send meth GET uri /api/txs/last ver HTTP/1.1 hdr host ${INGRESS_DOMAIN}
    http-check expect string cteId
  ogmios-api-healthcheck.tpl: |
    option httpchk
    http-check send meth GET hdr Host ${INGRESS_DOMAIN} hdr Connection Upgrade hdr Upgrade websocket hdr Sec-WebSocket-Key x3JJHMbDL1EzLkh9GBhXDw== hdr Sec-WebSocket-Version 13
    http-check expect status 101
  postgrest-api-healthcheck.tpl: |
    option external-check
    external-check path "/usr/bin:/bin:/tmp:/sbin:/usr/sbin"
    external-check command /configmap/postgrest-api-hc.sh
  koios-api-healthcheck.tpl: |
    option external-check
    external-check path "/usr/bin:/bin:/tmp:/sbin:/usr/sbin"
    external-check command /configmap/koios-api-hc.sh
  rosetta-api-healthcheck.tpl: |
    option external-check
    external-check path "/usr/bin:/bin:/tmp:/sbin:/usr/sbin"
    external-check command /configmap/rosetta-api-hc.sh
  drift-hc.sh: |
    #!/usr/bin/env bash

    export HOST_HEADER=$(echo $HAPROXY_PROXY_NAME | sed "s|^d.||g")
    HEALTHCHECK_TIMEOUT=5

    dbtip=$(timeout ${HEALTHCHECK_TIMEOUT} curl -s -k -fL -H "Host: ${HOST_HEADER}" -H "Accept: text/plain" "https://${HAPROXY_SERVER_ADDR}:${HAPROXY_SERVER_PORT}/block?select=time::text&order=id.desc&limit=1" 2>/dev/null)
    currtip=$(TZ='UTC' date "+%Y-%m-%d %H:%M:%S")
    if [[ -n "${dbtip}" ]] ; then
      TIP_DRIFT=$(( $(date -d "${currtip}" +%s) - $(date -d "${dbtip}" +%s) ))
      if [[ ${TIP_DRIFT} -lt 180 ]]
      then
        echo "OK (${HOST_HEADER}): Tip drift: $TIP_DRIFT (server: ${HAPROXY_SERVER_ADDR}:${HAPROXY_SERVER_PORT})"
        exit 0
      else
        echo "ERROR (${HOST_HEADER}): Tip too far (${TIP_DRIFT}), eliminating from availability (server: ${HAPROXY_SERVER_ADDR}:${HAPROXY_SERVER_PORT}, timeout: ${HEALTHCHECK_TIMEOUT})"
        exit 1
      fi
    else
      echo "ERROR: Failed polling for ${HAPROXY_SERVER_ADDR}:${HAPROXY_SERVER_PORT} (${HOST_HEADER}), could not query tip"
      exit 2
    fi
  graphql-api-hc.sh: |
    #!/usr/bin/env bash

    export HOST_HEADER=$(echo $HAPROXY_PROXY_NAME | sed 's|^d.||g')
    HAPROXY_PROXY_NAME=$(echo $HAPROXY_PROXY_NAME | sed 's|graphql|postgrest|g') bash /configmap/drift-hc.sh
    if [ $? -ne 0 ]; then exit 1; fi
    HEALTHCHECK_TIMEOUT=5

    timeout ${HEALTHCHECK_TIMEOUT} curl -s -k -H "Host: ${HOST_HEADER}" -H 'Accept: application/json' -H 'Content-Type: application/json' --data-binary '{"query":"# Write your query or mutation here\nquery chainTipAndCurrentEpochNumber {\n  cardano {\n    tip {\n      number\n    }\n    currentEpoch {\n      number\n    }\n  }\n}"}' https://${HAPROXY_SERVER_ADDR}:${HAPROXY_SERVER_PORT} | jq -r .data.cardano.tip.number 2>/dev/null | grep -q ^[0-9] || ( echo "ERROR (${HOST_HEADER}): no tip received (server: ${HAPROXY_SERVER_ADDR}:${HAPROXY_SERVER_PORT}, timeout: ${HEALTHCHECK_TIMEOUT})" && exit 2 )
  koios-api-hc.sh: |
    #!/usr/bin/env bash

    export HOST_HEADER=$(echo $HAPROXY_PROXY_NAME | sed 's|^d.||g')
    HAPROXY_PROXY_NAME=$(echo $HAPROXY_PROXY_NAME | sed 's|koios|postgrest|g') bash /configmap/drift-hc.sh
    if [ $? -ne 0 ]; then exit 1; fi
    HEALTHCHECK_TIMEOUT=5

    timeout ${HEALTHCHECK_TIMEOUT} curl -s -k -H "Host: ${HOST_HEADER}" "https://${HAPROXY_SERVER_ADDR}:${HAPROXY_SERVER_PORT}/rpc/tip" | grep -q hash || ( echo "ERROR (${HOST_HEADER}): no tip received (server: ${HAPROXY_SERVER_ADDR}:${HAPROXY_SERVER_PORT}, timeout: ${HEALTHCHECK_TIMEOUT})" && exit 2 )
  postgrest-api-hc.sh: |
    #!/usr/bin/env bash

    export HOST_HEADER=$(echo $HAPROXY_PROXY_NAME | sed 's|^d.||g')
    HAPROXY_PROXY_NAME=$(echo $HAPROXY_PROXY_NAME | sed 's|postgrest|postgrest|g') bash /configmap/drift-hc.sh
    if [ $? -ne 0 ]; then exit 1; fi
    HEALTHCHECK_TIMEOUT=5

    timeout ${HEALTHCHECK_TIMEOUT} curl -s -k -H "Host: ${HOST_HEADER}" "https://${HAPROXY_SERVER_ADDR}:${HAPROXY_SERVER_PORT}/epoch?limit=1" | grep -q out_sum || ( echo "ERROR (${HOST_HEADER}: no tip received (server: ${HAPROXY_SERVER_ADDR}:${HAPROXY_SERVER_PORT}, timeout: ${HEALTHCHECK_TIMEOUT})" && exit 2 )
  rosetta-api-hc.sh: |
    #!/usr/bin/env bash

    export HOST_HEADER=$(echo $HAPROXY_PROXY_NAME | sed 's|^d.||g')
    HAPROXY_PROXY_NAME=$(echo $HAPROXY_PROXY_NAME | sed 's|rosetta|postgrest|g') bash /configmap/drift-hc.sh
    if [ $? -ne 0 ]; then exit 1; fi
    HEALTHCHECK_TIMEOUT=5

    timeout ${HEALTHCHECK_TIMEOUT} curl -s -k -H "Host: ${HOST_HEADER}" "https://${HAPROXY_SERVER_ADDR}:${HAPROXY_SERVER_PORT}/" | grep -q message.*Route.GET.*404 || ( echo "ERROR (${HOST_HEADER}): bad response (server: ${HAPROXY_SERVER_ADDR}:${HAPROXY_SERVER_PORT}, timeout: ${HEALTHCHECK_TIMEOUT})" && exit 2 )
  api-config.json: |
    {
      "graphql-api": { "checkInterval": "16s" },
      "postgrest-api": { "checkInterval": "8s", "retryOn": "404" },
      "koios-api": { "checkInterval": "8s" },
      "rosetta-api": { "checkInterval": "20s" },
      "submit-api": { "checkInterval": "20s" },
      "explorer-api": { "checkInterval": "20s" },
      "ogmios-api": { "checkInterval": "20s" }
    }
  dis: |
      "postgrest-api": { "checkInterval": "8s", "retryOn": "conn-failure empty-response junk-response response-timeout 0rtt-rejected 500 502 503 504 404" },
  initContainer-entrypoint: |
    #!/bin/bash

    rm -f ${CONFIG_OUTPUT_DIR}/dandelion-haproxy.cfg
    
    # FRONTEND conf
    cat > ${CONFIG_OUTPUT_DIR}/dandelion-haproxy.cfg <<EOF
      frontend main
        mode http
        bind :80
        bind :443 ssl crt /usr/local/etc/ssl/self-signed-ssl.pem
        http-request redirect scheme https code 301 unless { ssl_fc }

        acl is_upgrade hdr(Connection) -i upgrade
        acl is_websocket hdr(Upgrade) -i websocket
    
        acl valid_hosts hdr(host) -m reg -i (${SUB_DOMAIN}.|)($(echo $APIS | sed 's/ /|/g')).($(echo $NETWORKS | sed 's/ /|/g')).${TOP_DOMAIN}$
        acl valid_hosts hdr(host) -m reg -i stats-ian.dandelion.link
        http-request deny if !valid_hosts
    
        use_backend %[req.hdr(Host),lower]
        default_backend no-match
    EOF
    
    # BACKENDS conf

    # stats
    cat >> ${CONFIG_OUTPUT_DIR}/dandelion-haproxy.cfg <<EOF
      backend stats-ian.dandelion.link
        mode http
        stats enable
        stats uri /
        stats hide-version
        stats refresh 10s
        stats admin if LOCALHOST

        option http-use-htx
        http-request use-service prometheus-exporter if { path /metrics }
    EOF

    for network in ${NETWORKS}
    do
    
      for api in ${APIS}
      do
    
        export FQDN="${SUB_DOMAIN}.${api}.${network}.${TOP_DOMAIN}"
        export INGRESS_DOMAIN="${api}.${network}.${TOP_DOMAIN}"
        API_CHECK_INTERVAL=$(jq -r .\"${api}\".checkInterval /configmap/api-config.json) 
        if [ "${API_CHECK_INTERVAL}" != "null" ]; then export HAPROXY_CHECK_INTERVAL="inter ${API_CHECK_INTERVAL}"; else unset HAPROXY_CHECK_INTERVAL; fi
        RETRY_ON=$(jq -r .\"${api}\".retryOn /configmap/api-config.json) 
        if [ "${RETRY_ON}" != "null" ]; then export HAPROXY_BACKEND_RETRY_ON="${RETRY_ON}"; else unset HAPROXY_BACKEND_RETRY_ON; fi
    
        jq -rc .Providers[] ${TOPOLOGY_DIR}/topology-${network}.json | while read provider
        do
          PROVIDER_NAME=$(echo $provider | jq -r .name)
          PROVIDER_HOST=$(echo $provider | jq -r .addr)
          PROVIDER_PORT=$(echo $provider | jq -r .port)
          PROVIDER_PROTO=$(echo $provider | jq -r .protocol)
          PROVIDER_SNI=$(echo $provider | jq -r .sni)

          if [ "${PROVIDER_PROTO}" == "https" ]
          then
            if [ "${PROVIDER_SNI}" != "null" ]
            then
              SNI_FQDN=${PROVIDER_SNI}
            else
              SNI_FQDN=${INGRESS_DOMAIN}
            fi
            CHECK="check ${HAPROXY_CHECK_INTERVAL:-inter 20s} ssl verify none check-sni ${INGRESS_DOMAIN} sni str(${SNI_FQDN})"
          else
            CHECK="check ${HAPROXY_CHECK_INTERVAL}"
          fi
          echo "    server ${PROVIDER_NAME}    ${PROVIDER_HOST}:${PROVIDER_PORT} ${CHECK} maxconn ${HAPROXY_MAXCONN_PER_BACKEND} cookie core"
        done > /tmp/${FQDN}.backends.haproxy.cfg 
      
        cat > /tmp/${FQDN}.haproxy.cfg <<EOF
      backend ${FQDN} 
        mode http
        balance static-rr
        retry-on ${HAPROXY_BACKEND_RETRY_ON:-all-retryable-errors}

        $(envsubst < ${HEALTHCHECKS_TEMPLATE_DIR}/${api}-healthcheck.tpl)
    
        cookie network insert
        http-request set-header X-Client-IP %[src]
        http-request set-header X-Forwarded-Host %[req.hdr(Host)]
        http-request set-header Origin https://${INGRESS_DOMAIN}
        http-request set-header Host ${INGRESS_DOMAIN}
        http-response set-header Access-Control-Allow-Origin *
    
    $(cat /tmp/${FQDN}.backends.haproxy.cfg)
    
    EOF

      cat /tmp/${FQDN}.haproxy.cfg >> ${CONFIG_OUTPUT_DIR}/dandelion-haproxy.cfg
      done
    done
