Skip to content

Commit

Permalink
feat(#8551): removes python from cht-deploy
Browse files Browse the repository at this point in the history
  • Loading branch information
henokgetachew authored Aug 20, 2024
1 parent f784c0d commit 4692ee7
Show file tree
Hide file tree
Showing 15 changed files with 829 additions and 282 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@
"unit-nginx": "cd nginx/tests && make test",
"unit-haproxy": "cd haproxy/tests && make test",
"unit-haproxy-healthcheck": "cd haproxy-healthcheck && make test",
"unit-cht-deploy": "cd scripts/deploy && npm test",
"wdio-default-mobile-local": "export VERSION=$(node ./scripts/build/get-version.js) && ./scripts/build/build-service-images.sh && wdio run ./tests/e2e/default-mobile/wdio.conf.js --suite=all",
"wdio-local": "export VERSION=$(node ./scripts/build/get-version.js) && ./scripts/build/build-service-images.sh && wdio run ./tests/e2e/default/wdio.conf.js",
"-- CI SCRIPTS ": "-----------------------------------------------------------------------------------------------",
"build": "./scripts/build/build-ci.sh",
"ci-compile": "node scripts/ci/check-versions.js && node scripts/build/cli npmCiModules && npm run lint && npm run unit-nginx && npm run unit-haproxy && npm run unit-haproxy-healthcheck && npm run build && npm run integration-api && npm run unit",
"ci-compile": "node scripts/ci/check-versions.js && node scripts/build/cli npmCiModules && npm run lint && npm run unit-nginx && npm run unit-haproxy && npm run unit-haproxy-healthcheck && npm run unit-cht-deploy && npm run build && npm run integration-api && npm run unit",
"ci-integration-all": "mocha --config tests/integration/.mocharc-all.js",
"ci-integration-all-k3d": "mocha --config tests/integration/.mocharc-k3d.js",
"ci-integration-sentinel": "mocha --config tests/integration/.mocharc-sentinel.js",
Expand Down
8 changes: 8 additions & 0 deletions scripts/deploy/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-console": "off"
}
}
2 changes: 2 additions & 0 deletions scripts/deploy/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.tar.gz
*.tgz
124 changes: 83 additions & 41 deletions scripts/deploy/cht-deploy
Original file line number Diff line number Diff line change
@@ -1,41 +1,83 @@
#!/bin/bash

# Check if Python is installed
if ! command -v python3 &> /dev/null ; then
echo "Python is not installed. Please install Python 3."
exit 1
fi

# Check if pip is installed
if ! command -v pip3 &> /dev/null ; then
echo "pip is not installed. Please install pip for Python 3."
exit 1
fi

# Check if Invoke is installed
if ! python3 -c "import invoke" &> /dev/null ; then
echo "Invoke is not installed. Installing it..."
pip3 install invoke --quiet
fi

# Check if PyYAML is installed
if ! python3 -c "import yaml" &> /dev/null ; then
echo "PyYAML is not installed. Installing it..."
pip3 install PyYAML --quiet
fi

# Check if requests is installed
if ! python3 -c "import requests" &> /dev/null ; then
echo "Requests is not installed. Installing it..."
pip3 install requests --quiet
fi

# Validate that -f argument is provided
if [[ $1 != "-f" || -z $2 ]]; then
echo "No values file provided. Please specify a values file using -f <file>"
exit 1
fi

# Pass command line arguments to invoke script
# shellcheck disable=SC2068 # wontfix script will be replaced "soon"
invoke install $@
#!/usr/bin/env node

import { install } from './src/install.js';
import fs from 'fs';
import semver from 'semver';
import path from 'path';

import { dirname } from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const validateNodeVersion = function() {
const packageJsonPath = path.resolve(__dirname, 'package.json');

try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const requiredNodeVersion = packageJson.engines && packageJson.engines.node;

if (requiredNodeVersion && !semver.satisfies(process.version, requiredNodeVersion)) {
console.error(`Invalid Node.js version. Required: ${requiredNodeVersion}. Current: ${process.version}`);
process.exit(1);
}
} catch (err) {
console.error('Error reading package.json:', err.message);
process.exit(1);
}
};

const validateArguments = function() {
const args = process.argv.slice(2);
if (args.length < 2 || args[0] !== '-f' || !args[1]) {
console.error('No values file provided. Please specify a values file using -f <file>');
process.exit(1);
}
if (validateFileExists(args[1])) {
return args;
}
};

const validateFileExists = function(filePath) {
try {
fs.accessSync(filePath);
return true;
} catch (err) {
console.error(`File not found: ${filePath}`);
process.exit(1);
}
};

const runInstallScript = async function(args) {
try {
const valuesFilePath = args[1];
await install(valuesFilePath);
} catch (err) {
console.error('Error executing the install script:', err.message);
console.error(JSON.stringify(err));
process.exit(1);
}
};

const main = async function() {
validateNodeVersion();
const args = validateArguments();
await runInstallScript(args);
};

if (import.meta.url === `file://${process.argv[1]}`) { //Make sure the script is being run directly and not being imported
main().catch((err) => {
console.error('An error occurred:', err.message);
console.error(JSON.stringify(err));
process.exit(1);
});
}

export {
main,
validateNodeVersion,
validateArguments,
validateFileExists,
runInstallScript
};
54 changes: 54 additions & 0 deletions scripts/deploy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "@medic/cht-deploy",
"version": "1.0.10",
"description": "## Deploy the CHT",
"main": "src/install.js",
"directories": {
"test": "tests"
},
"engines": {
"node": ">=20.11.0",
"npm": ">=10.2.4"
},
"bin": {
"cht-deploy": "./cht-deploy",
"upload": "./cht-deploy",
"get-all-logs": "./troubleshooting/get-all-logs",
"describe-deployment": "./troubleshooting/describe-deployment",
"list-all-resources": "./troubleshooting/list-all-resources",
"list-deployments": "./troubleshooting/list-deployments",
"restart-deployment": "./troubleshooting/restart-deployment",
"view-logs": "./troubleshooting/view-logs"
},
"scripts": {
"test": "mocha 'tests/*.js'"
},
"mocha": {
"timeout": 5000,
"recursive": true
},
"files": [
"src",
"cht-deploy",
"troubleshooting",
"README.md",
"package.json"
],
"keywords": [],
"author": "",
"license": "AGPL-3.0-only",
"type": "module",
"devDependencies": {
"@kubernetes/client-node": "^0.19.0",
"@sinonjs/fake-timers": "^11.2.2",
"chai": "^4.4.1",
"chai-as-promised": "^7.1.2",
"mocha": "^10.4.0",
"sinon": "^17.0.1"
},
"dependencies": {
"js-yaml": "^4.1.0",
"node-fetch": "^3.3.2",
"semver": "^7.6.2"
}
}
122 changes: 122 additions & 0 deletions scripts/deploy/src/certificate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import fs from 'fs';
import { execSync } from 'child_process';
import fetch from 'node-fetch';
import { UserRuntimeError, CertificateError } from './error.js';
import config from './config.js';

const CERT_SOURCES = {
FILE: 'specify-file-path',
MY_IP: 'my-ip-co',
EKS: 'eks-medic'
};

const cert = config.CERT_FILE;
const key = config.KEY_FILE;

const obtainCertificateAndKey = async function(values) {
console.log('Obtaining certificate...');
const certSource = values.cert_source || '';

const handlers = {
[CERT_SOURCES.FILE]: () => handleFileSource(values),
[CERT_SOURCES.MY_IP]: () => handleMyIpSource(),
[CERT_SOURCES.EKS]: () => {} // No action needed for 'eks-medic'
};

const handler = handlers[certSource];
if (!handler) {
throw new UserRuntimeError(`cert_source must be one of: ${Object.values(CERT_SOURCES).join(', ')}`);
}

await handler();
};

const handleFileSource = function({ certificate_crt_file_path, certificate_key_file_path }) {
if (!certificate_crt_file_path || !certificate_key_file_path) {
throw new CertificateError('certificate_crt_file_path and certificate_key_file_path must be set for file source');
}

copyFile(certificate_crt_file_path, cert);
copyFile(certificate_key_file_path, key);
};

const handleMyIpSource = async function() {
const [crtData, keyData] = await Promise.all([
fetchData(`${config.CERT_API_URL}/fullchain`),
fetchData(`${config.CERT_API_URL}/key`)
]);

writeFile(cert, crtData);
writeFile(key, keyData);
};

const copyFile = function(src, dest) {
try {
fs.copyFileSync(src, dest);
} catch (error) {
throw new CertificateError(`Failed to copy file from ${src} to ${dest}: ${error.message}`);
}
};

const writeFile = function(filename, data) {
try {
fs.writeFileSync(filename, data);
} catch (error) {
throw new CertificateError(`Failed to write file ${filename}: ${error.message}`);
}
};

const fetchData = async function(url) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), config.FETCH_TIMEOUT);
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return await response.text();
} catch (error) {
if (error.name === 'AbortError') {
throw new CertificateError(`Request to ${url} timed out`);
}
throw new CertificateError(`Failed to fetch data from ${url}: ${error.message}`);
}
};

const createSecret = async function (namespace, values) {
console.log('Checking if secret api-tls-secret already exists...');
try {
execSync(`kubectl get secret api-tls-secret -n ${namespace}`, { stdio: 'inherit' }); //NoSONAR
console.log('TLS secret already exists. Skipping creation.');
return;
} catch (err) {
if (err.message.includes('NotFound')) {
console.log('Secret does not exist. Creating Secret from certificate and key...');
} else {
throw err;
}
}

await obtainCertificateAndKey(values);

execSync( //NoSONAR
`kubectl -n ${namespace} create secret tls api-tls-secret --cert=${cert} --key=${key}`, //NoSONAR
{ stdio: 'inherit' }
);
cleanupFiles();
};

const cleanupFiles = function() {
deleteFile(cert);
deleteFile(key);
};

const deleteFile = function(filename) {
try {
if (fs.existsSync(filename)) {
fs.unlinkSync(filename);
}
} catch (error) {
console.error(`Failed to delete file ${filename}: ${error.message}`);
}
};

export { obtainCertificateAndKey, createSecret };
11 changes: 11 additions & 0 deletions scripts/deploy/src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default {
MEDIC_REPO_NAME: process.env.MEDIC_REPO_NAME || 'medic',
MEDIC_REPO_URL: process.env.MEDIC_REPO_URL || 'https://docs.communityhealthtoolkit.org/helm-charts',
CHT_CHART_NAME: process.env.CHT_CHART_NAME || 'medic/cht-chart-4x',
DEFAULT_CHART_VERSION: process.env.DEFAULT_CHART_VERSION || '1.0.*',
IMAGE_TAG_API_URL: process.env.IMAGE_TAG_API_URL || 'https://staging.dev.medicmobile.org/_couch/builds_4',
CERT_FILE: process.env.CERT_FILE || 'certificate.crt',
KEY_FILE: process.env.KEY_FILE || 'private.key',
CERT_API_URL: process.env.CERT_API_URL || 'https://local-ip.medicmobile.org',
FETCH_TIMEOUT: parseInt(process.env.FETCH_TIMEOUT, 10) || 5000,
};
13 changes: 13 additions & 0 deletions scripts/deploy/src/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class UserRuntimeError extends Error {
constructor(message) {
super(message);
this.name = 'UserRuntimeError';
}
}

export class CertificateError extends Error {
constructor(message) {
super(message);
this.name = 'CertificateError';
}
}
Loading

0 comments on commit 4692ee7

Please sign in to comment.