UNPKG

k8s-features

Version:

A Cucumber-js base library for Kubernetes Gherkin tests, with base world class, basic steps, reusable utility functions and k8s client

239 lines (217 loc) 7.14 kB
const { PodEnvFromSecretPatcher } = require('../k8s/patcher/podEnvFromSecretPatcher.cjs'); const { PodEnvFromConfigMapPatcher } = require('../k8s/patcher/podEnvFromConfigMapPatcher.cjs'); const { PodEnvFixedPatcher } = require('../k8s/patcher/podEnvFixedPatcher.cjs'); const { makeid } = require('../util/makeId.cjs'); const { PodMountSecretPatcher } = require('../k8s/patcher/podMountSecretPatcher.cjs'); const { PodMountConfigMapPatcher } = require('../k8s/patcher/podMountConfigMapPatcher.cjs'); const { inspect } = require('node:util'); class RedisCmdGivenParams { host = false; port = false; auth = false; tls = false; ca = ''; version = ''; clusterMode = false; } /** * @param {string} rowName * @param {string[]} row * @param {number} index * @param {string} colName */ function mustHaveColumn(rowName, row, index, colName) { if (row.length < index - 1) { throw new Error(`Redis table for ${rowName} missing column ${index} as ${colName}`); } const val = row[index]; if (!val) { throw new Error(`Redis table for ${rowName} has emprty column ${index} as ${colName}`); } } /** * @param {import("./world.cjs").MyWorld} world * @param {string} envVarName * @param {string[]} row * @returns {import("../k8s/patcher/types.cjs").AbstractKubernetesObjectPatcher} */ function envPatcherForParam(world, envVarName, row) { const valueIndicator = world.templateWithThrow(row[1]); switch (valueIndicator) { case 'Secret': mustHaveColumn(envVarName, row, 2, 'secret name'); mustHaveColumn(envVarName, row, 3, 'secret key'); return new PodEnvFromSecretPatcher(envVarName, world.templateWithThrow(row[2]), world.templateWithThrow(row[3])); case 'ConfigMap': mustHaveColumn(envVarName, row, 2, 'configmap name'); mustHaveColumn(envVarName, row, 3, 'configmap key'); return new PodEnvFromConfigMapPatcher(envVarName, world.templateWithThrow(row[2]), world.templateWithThrow(row[3])); default: return new PodEnvFixedPatcher(envVarName, valueIndicator); } } /** * @typedef VolumePatcherForParamResult * @property {KubernetesObjectPatcher} patcher * @property {string} key */ /** * @param {import("./world.cjs").MyWorld} world * @param {string} rowName * @param {string[]} row * @param {string} volumeName * @returns {VolumePatcherForParamResult} */ function volumePatcherForParam(world, rowName, row, volumeName) { const valueIndicator = world.templateWithThrow(row[1]); switch (valueIndicator) { case 'Secret': mustHaveColumn(rowName, row, 2, 'secret name'); mustHaveColumn(rowName, row, 3, 'secret key'); return { patcher: new PodMountSecretPatcher(world.templateWithThrow(row[2]), volumeName), key: world.templateWithThrow(row[3]), } case 'ConfigMap': mustHaveColumn(rowName, row, 2, 'configmap name'); mustHaveColumn(rowName, row, 3, 'configmap key'); return { patcher: new PodMountConfigMapPatcher(world.templateWithThrow(row[2]), volumeName), key: world.templateWithThrow(row[3]), } default: throw new Error(`The Redis param ${rowName} must be specified in secret or configmap`); } } /** * @param {import("./world.cjs").MyWorld} world * @param {string} cmd * @param {string} expectedOutput * @param {import("@cucumber/cucumber").DataTable} dataTable * @returns {Promise} */ async function redisCmd(world, cmd, expectedOutput, dataTable) { /** * @type {KubernetesObjectPatcher[]} */ const patchers = []; const setValues = new RedisCmdGivenParams(); for (let row of dataTable.raw()) { switch (row[0]) { case 'Host': setValues.host = true; patchers.push(envPatcherForParam(world, 'HOST', row)); break; case 'Port': setValues.port = true; patchers.push(envPatcherForParam(world, 'PORT', row)); break; case 'Auth': setValues.auth = true; patchers.push(envPatcherForParam(world, 'REDISCLI_AUTH', row)); break; case 'TLS': mustHaveColumn('TLS', row, 1, 'tls enabled'); setValues.tls = ['true', 'yes', 'on', '1'].includes(world.templateWithThrow(row[1]).toLocaleLowerCase()); break; case 'CA': { const patch = volumePatcherForParam(world, 'CA', row, 'cacert'); setValues.ca = patch.key; patchers.push(patch.patcher); } break; case 'Version': mustHaveColumn('Version', row, 1, 'version'); setValues.version = world.templateWithThrow(row[1]); break; case 'ClusterMode': mustHaveColumn('ClusterMode', row, 1, 'cluster mode'); setValues.clusterMode = ['true', 'yes', 'on', '1'].includes(world.templateWithThrow(row[1]).toLocaleLowerCase()); break; default: throw new Error(`Unknown Redis parameter ${row[0]}`); } } if (!setValues.host) { throw new Error(`Mandatory Redis Host param is not set`); } const name = `k8f${makeid(8)}`; const namespace = world.parameters.namespace ?? 'default'; /** * @type {string[]} */ const scriptLines = []; if (setValues.tls && setValues.ca == '') { scriptLines.push( `apt-get update`, `apt-get install -y ca-certificates`, `update-ca-certificates`, ) } let command = `redis-cli -h $HOST`; if (setValues.port) { command += ` -p $PORT`; } if (setValues.tls) { command += ` --tls`; } if(setValues.clusterMode) { command += ' -c' } if (setValues.ca != '') { command += ` --cacert /mnt/cacert/${setValues.ca}`; } command += ` ${cmd}`; scriptLines.push(command); if (setValues.version == '') { setValues.version = 'latest'; } await world.addWatchedResources({ alias: name, kind: 'Pod', apiVersion: 'v1', name, namespace, }) /** @type {import('@kubernetes/client-node').KubernetesObject} */ let podObj; /** @type {import('@kubernetes/client-node').KubernetesObject} */ let cmObj; try { ({ podObj, cmObj } = await world.createPod(name, namespace, scriptLines, `redis:${setValues.version}`, ...patchers)); } catch (err) { console.error(inspect(err)); throw new Error(`Error creating Pod for redis: ${err}`, {cause: err}); } await world.eventuallyValueIsOk( `${name}.status.phase == "Succeeded"`, `${name}.status.phase == "Failed"` ); /** @type {string} */ let logs; try { logs = await world.getLogs(name, namespace, name); } catch (err) { console.error(inspect(err)); throw new Error(`Error getting Pod logs for redis: ${err}`, {cause: err}); } try { await world.delete(podObj); } catch (err) { console.error(inspect(err)); throw new Error(`Error deleting Pod for redis: ${err}`, {cause: err}); } try { await world.delete(cmObj); } catch (err) { console.error(inspect(err)); throw new Error(`Error deleting ConfigMap for redis: ${err}`, {cause: err}); } if (logs.includes(expectedOutput)) { return; } throw new Error(`Redis command ${cmd} failed:\n${logs}`); } module.exports = { redisCmd, };