|
#!/usr/bin/env node |
|
const path = require("path"); |
|
const chalk = require("chalk"); |
|
const k8s = require("@kubernetes/client-node"); |
|
const { spawn } = require("child_process"); |
|
const _ = require("lodash"); |
|
const { EventEmitter } = require("stream"); |
|
|
|
class ManagedProcess extends EventEmitter { |
|
constructor(command, args) { |
|
super(); |
|
this.command = command; |
|
this.args = args; |
|
this._process = null; |
|
this.receivedKill = false; |
|
|
|
process.on("SIGINT", this.handleKill.bind(this)); |
|
process.on("SIGTERM", this.handleKill.bind(this)); |
|
} |
|
|
|
isRunning() { |
|
return this._process && this._process.exitCode === null; |
|
} |
|
|
|
start() { |
|
if (this.isRunning()) { |
|
console.log(chalk.yellow("cant start, the process is already running")); |
|
// process.exit(1); |
|
} |
|
console.log(chalk.green.bold(`> Starting ${this.command}...`)); |
|
this._process = spawn(this.command, this.args); |
|
this._process.ref(); |
|
|
|
this._process.stdout.on("data", (data) => { |
|
process.stdout.write(data.toString()); |
|
}); |
|
|
|
this._process.stderr.on("data", (data) => { |
|
process.stderr.write(data.toString()); |
|
}); |
|
|
|
this._process.on("error", (error) => { |
|
console.log(chalk.red("!! err: ", err)); |
|
}); |
|
|
|
this._process.once("close", (code, signal) => { |
|
if (code === 0) return; |
|
console.log( |
|
chalk.red( |
|
`!! child process exited with code: ${code}, signal: ${signal}` |
|
) |
|
); |
|
}); |
|
} |
|
|
|
stop() { |
|
console.log(chalk.red.bold(`> Stopping ${this.command}...`)); |
|
this._process.kill("SIGTERM"); |
|
this._process = null; |
|
} |
|
|
|
die() { |
|
if (!this._process) { |
|
this.restart(); |
|
return; |
|
} |
|
|
|
this._process.once("close", () => { |
|
if (this.receivedKill) { |
|
process.exit(1); |
|
return; |
|
} |
|
this.restart(); |
|
}); |
|
} |
|
|
|
handleKill() { |
|
console.log(chalk.red.bold(`> Killing ${this.command}...`)); |
|
this.receivedKill = true; |
|
this.die(); |
|
} |
|
|
|
restart() { |
|
console.log(chalk.green.bold(`> Restarting ${this.command}...`)); |
|
|
|
// try starting the process if it is not running yet |
|
if (!this._process) { |
|
setTimeout(this.start.bind(this), 2000); |
|
return; |
|
} |
|
|
|
this._process.once("close", () => { |
|
this.start(); |
|
}); |
|
this.stop(); |
|
} |
|
} |
|
|
|
function restartK8SWatcher(watchReq, processManager) { |
|
console.log(chalk.green.bold(">> Restarting k8s events watcher in 5s...")); |
|
watchReq.abort(); |
|
setTimeout(() => watchK8SEvents(processManager), 5000); |
|
} |
|
|
|
function watchK8SEvents(processManager) { |
|
const debouncedRestartProcess = _.debounce( |
|
processManager.restart.bind(processManager), |
|
5000 |
|
); |
|
const debouncedStartProcess = _.debounce( |
|
processManager.start.bind(processManager), |
|
2000 |
|
); |
|
|
|
const watch = new k8s.Watch(kc); |
|
let watchReq; |
|
watch |
|
.watch( |
|
`/api/v1/namespaces/${currentNamespace}/pods`, |
|
{ |
|
labelSelector: "app.kubernetes.io/name=devspace-app", |
|
}, |
|
(type, apiObj) => { |
|
console.log( |
|
chalk.blue.bold( |
|
`>> Detected change (${type}) in ${apiObj.metadata.name}...` |
|
) |
|
); |
|
if (!processManager.isRunning()) { |
|
debouncedStartProcess(); |
|
return; |
|
} |
|
|
|
if (type === "ADDED" || type === "DELETED") { |
|
debouncedRestartProcess(); |
|
} |
|
}, |
|
(err) => { |
|
console.log( |
|
chalk.red("!! There was an error", JSON.stringify({ err })) |
|
); |
|
restartK8SWatcher(watchReq, processManager); |
|
} |
|
) |
|
.then((req) => { |
|
console.log( |
|
chalk.green.bold( |
|
`>> Watching k8s events for ${kc.getCurrentContext()}:${currentNamespace}` |
|
) |
|
); |
|
watchReq = req; |
|
}) |
|
.catch((err) => { |
|
console.log(chalk.red("!! Watcher error: ", err)); |
|
restartK8SWatcher(watchReq, processManager); |
|
}); |
|
|
|
process.on("exit", () => { |
|
watchReq.abort(); |
|
}); |
|
} |
|
|
|
const kc = new k8s.KubeConfig(); |
|
kc.loadFromDefault(); |
|
const currentContextConfig = kc.getContextObject(kc.getCurrentContext()); |
|
const currentNamespace = currentContextConfig.namespace; |
|
|
|
const kubefwdProcessManager = new ManagedProcess("kubefwd", [ |
|
"svc", |
|
"-n", |
|
currentNamespace, |
|
]); |
|
|
|
watchK8SEvents(kubefwdProcessManager); |