---
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ template "kibana.fullname" . }}-helm-scripts
  labels: {{ include "kibana.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": pre-install,pre-upgrade,post-delete
    "helm.sh/hook-delete-policy": hook-succeeded
    {{- if .Values.annotations }}
    {{- range $key, $value := .Values.annotations }}
    {{ $key }}: {{ $value | quote }}
    {{- end }}
    {{- end }}
data:
  manage-es-token.js: |
    const https = require('https');
    const fs = require('fs');

    // Read environment variables
    function getEnvVar(name) {
      if (!process.env[name]) {
        throw new Error(name + ' environment variable is missing')
      }
      return process.env[name]
    }

    // Elasticsearch API
    const esPath = '_security/service/elastic/kibana/credential/token/{{ template "kibana.fullname" . }}';
    const esUrl = '{{ .Values.elasticsearchHosts }}' + '/' + esPath
    const esUsername = getEnvVar('ELASTICSEARCH_USERNAME');
    const esPassword = getEnvVar('ELASTICSEARCH_PASSWORD');
    const esAuth = esUsername + ':' + esPassword;
    const esCaFile = getEnvVar('ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES');
    const esCa = fs.readFileSync(esCaFile);

    // Kubernetes API
    const k8sHostname = getEnvVar('KUBERNETES_SERVICE_HOST');
    const k8sPort = getEnvVar('KUBERNETES_SERVICE_PORT_HTTPS');
    const k8sPostSecretPath = 'api/v1/namespaces/{{ .Release.Namespace }}/secrets';
    const k8sDeleteSecretPath = 'api/v1/namespaces/{{ .Release.Namespace }}/secrets/{{ template "kibana.fullname" . }}-es-token';
    const k8sPostSecretUrl = `https://${k8sHostname}:${k8sPort}/${k8sPostSecretPath}`;
    const k8sDeleteSecretUrl = `https://${k8sHostname}:${k8sPort}/${k8sDeleteSecretPath}`;
    const k8sBearer = fs.readFileSync('/run/secrets/kubernetes.io/serviceaccount/token');
    const k8sCa = fs.readFileSync('/run/secrets/kubernetes.io/serviceaccount/ca.crt');

    // Post Data
    const esTokenDeleteOptions = {
      method: 'DELETE',
      auth: esAuth,
      ca: esCa,
    };
    const esTokenCreateOptions = {
      method: 'POST',
      auth: esAuth,
      ca: esCa,
    };
    const secretCreateOptions = {
      method: 'POST',
      ca: k8sCa,
      headers: {
        'Authorization': 'Bearer ' + k8sBearer,
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      }
    };
    const secretDeleteOptions = {
      method: 'DELETE',
      ca: k8sCa,
      headers: {
        'Authorization': 'Bearer ' + k8sBearer,
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      }
    };

    // With thanks to https://stackoverflow.com/questions/57332374/how-to-chain-http-request
    function requestPromise(url, httpsOptions, extraOptions = {}) {
      return new Promise((resolve, reject) => {
        const request = https.request(url, httpsOptions, response => {

          console.log('statusCode:', response.statusCode);

          let isSuccess = undefined;

          if (typeof(extraOptions.extraStatusCode) != "undefined" && extraOptions.extraStatusCode != null) {
            isSuccess = response.statusCode >= 200 && response.statusCode < 300 || response.statusCode == extraOptions.extraStatusCode;
          } else {
            isSuccess = response.statusCode >= 200 && response.statusCode < 300;
          }

          let data = '';
          response.on('data', chunk => data += chunk); // accumulate data
          response.once('end', () => isSuccess ? resolve(data) : reject(data));  // resolve promise here
        });

        request.once('error', err => {
          // This won't log anything for e.g. an HTTP 404 or 500 response,
          // since from HTTP's point-of-view we successfully received a
          // response.
          console.log(`${httpsOptions.method} ${httpsOptions.path} failed: `, err.message || err);
          reject(err);  // if promise is not already resolved, then we can reject it here
        });

        if (typeof(extraOptions.payload) != "undefined") {
          request.write(extraOptions.payload);
        }
        request.end();
      });
    }

    function createEsToken() {
      // Chaining requests
      console.log('Cleaning previous token');
      // 404 status code is accepted if there is no previous token to clean
      return requestPromise(esUrl, esTokenDeleteOptions, {extraStatusCode: 404}).then(() => {
        console.log('Creating new token');
        return requestPromise(esUrl, esTokenCreateOptions).then(response => {
          const body = JSON.parse(response);
          const token = body.token.value

          // Encode the token in base64
          const base64Token = Buffer.from(token, 'utf8').toString('base64');

          // Prepare the k8s secret
          const secretData = JSON.stringify({
            "apiVersion": "v1",
            "kind": "Secret",
            "metadata": {
              "namespace": "{{ .Release.Namespace }}",
              "name": "{{ template "kibana.fullname" . }}-es-token",
            },
            "type": "Opaque",
            "data": {
              "token": base64Token,
            }
          })

          // Create the k8s secret
          console.log('Creating K8S secret');
          return requestPromise(k8sPostSecretUrl, secretCreateOptions, {payload: secretData})
        });
      });
    }

    function cleanEsToken() {
      // Chaining requests
      console.log('Cleaning token');
      return requestPromise(esUrl, esTokenDeleteOptions).then(() => {
        // Create the k8s secret
        console.log('Delete K8S secret');
        return requestPromise(k8sDeleteSecretUrl, secretDeleteOptions)
      });
    }

    const command = process.argv[2];
    switch (command) {
      case 'create':
        console.log('Creating a new Elasticsearch token for Kibana')
        createEsToken().catch(err => {
          console.error(err);
          process.exit(1);
        });
        break;
      case 'clean':
        console.log('Cleaning the Kibana Elasticsearch token')
        cleanEsToken().catch(err => {
          console.error(err);
          process.exit(1);
        });
        break;
      default:
        console.log('Unknown command');
        process.exit(1);
    }
