Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
mook-as committed Jan 11, 2021
1 parent 5a6090d commit 2f0b241
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 90 deletions.
46 changes: 21 additions & 25 deletions background.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ app.whenReady().then(() => {
console.log(cfg);
k8smanager = newK8sManager(cfg.kubernetes);

k8smanager.start().then((code) => {
console.log(`1: Child exited with code ${code}`);
}, handleFailure);
k8smanager.start().catch(handleFailure);

// Set up protocol handler for app://
// This is needed because in packaged builds we'll not be allowed to access
Expand Down Expand Up @@ -114,7 +112,7 @@ ipcMain.on('k8s-reset', async (event, arg) => {
try {
if (arg === 'Reset Kubernetes to default') {
// If not in a place to restart than skip it
if (k8smanager.state != K8s.State.STARTED && k8smanager.state != K8s.State.STOPPED) {
if ([K8s.State.STARTED, K8s.State.READY, K8s.State.STOPPED].indexOf(k8smanager.state) >= 0) {
return;
}
let code = await k8smanager.stop();
Expand All @@ -132,39 +130,36 @@ ipcMain.on('k8s-reset', async (event, arg) => {
// The desired Kubernetes version might have changed
k8smanager = newK8sManager(cfg.kubernetes);

code = await k8smanager.start();
await k8smanager.start();
try {
event.reply('k8s-check-state', k8smanager.state);
} catch (err) {
console.log(err);
console.error(err);
}
console.log(`Starting minikube exited with code ${code}`);
}
} catch (ex) {
handleFailure(ex);
}
});

ipcMain.on('k8s-restart', async (event) => {
if (k8smanager.state != K8s.State.STARTED && k8smanager.state != K8s.State.STOPPED) {
return;
}

try {
if (k8smanager.state === K8s.State.STOPPED) {
let code = await k8smanager.start();
console.log(`3: Child exited with code ${code}`);
} else if (k8smanager.state === K8s.State.STARTED) {
await k8smanager.stop();
// The desired Kubernetes version might have changed
k8smanager = newK8sManager(cfg.kubernetes);

await k8smanager.start();
try {
event.reply('k8s-check-state', k8smanager.state);
} catch (err) {
console.log(err);
}
switch (k8smanager.state) {
case K8s.State.STOPPED:
await k8smanager.start();
break;
case K8s.State.STARTED, K8s.State.READY:
await k8smanager.stop();
// The desired Kubernetes version might have changed
k8smanager = newK8sManager(cfg.kubernetes);

await k8smanager.start();
try {
event.reply('k8s-check-state', k8smanager.state);
} catch (err) {
console.log(err);
}
break;
}
} catch (ex) {
handleFailure(ex);
Expand Down Expand Up @@ -251,6 +246,7 @@ function newK8sManager(cfg) {
let mgr = K8s.factory(cfg);
mgr.on("state-changed", (state) => {
tray.k8sStateChanged(state);
window.send("k8s-check-state", state);
});

return mgr;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.1",
"scripts": {
"lint": "vue-cli-service lint",
"dev": "vue-cli-service electron:serve",
"dev": "node --unhandled-rejections=strict node_modules/.bin/vue-cli-service electron:serve",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"generateReleases": "node ./scripts/generateReleaseList.js",
Expand Down
23 changes: 5 additions & 18 deletions src/components/K8s.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<option v-for="item in versions" :key="item" :value="item">{{ item }}</option>
</select> Kubernetes version
<hr>
<button @click="reset" :disabled="isDisabled" class="role-destructive btn-sm" :class="{ 'btn-disabled': isDisabled }">Reset Kubernetes</button>
<button @click="reset" :disabled="canReset" class="role-destructive btn-sm" :class="{ 'btn-disabled': canReset }">Reset Kubernetes</button>
Resetting Kubernetes to default will delete all workloads and configuration
<hr>
<Checkbox :label="'link to /usr/local/bin/kubectl'"
Expand Down Expand Up @@ -48,23 +48,20 @@ export default {
},
computed: {
isDisabled: function() {
if (this.state != K8s.State.STARTED) {
return true;
}
return false;
canReset: function() {
return [K8s.State.STARTED, K8s.State.READY].indexOf(this.state) >= 0;
}
},
methods: {
// Reset a Kubernetes cluster to default at the same version
reset() {
ipcRenderer.send('k8s-reset', 'Reset Kubernetes to default');
this.state = K8s.State.STOPPING;
ipcRenderer.send('k8s-reset', 'Reset Kubernetes to default');
},
restart() {
ipcRenderer.send('k8s-restart', 'Restart Kubernetes');
this.state = K8s.State.STOPPING;
ipcRenderer.send('k8s-restart', 'Restart Kubernetes');
},
onChange(event) {
if (event.target.value != this.settings.kubernetes.version) {
Expand Down Expand Up @@ -104,16 +101,6 @@ export default {
});
ipcRenderer.send('install-state', 'kubectl');
ipcRenderer.send('install-state', 'helm');
if (this.state != K8s.State.STARTED) {
let tmr = setInterval(() => {
let stt = ipcRenderer.sendSync('k8s-state');
if (stt === K8s.State.STARTED) {
this.state = stt;
clearInterval(tmr);
}
}, 5000)
}
},
}
</script>
Expand Down
40 changes: 25 additions & 15 deletions src/k8s-engine/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,38 +42,44 @@ class KubeClient {
#_coreV1API = null;

/**
* Return a pod for homestead that is ready to receive connections.
* Return a pod that is part of a given endpoint and ready to receive traffic.
* @param {string} namespace The namespace in which to look for resources.
* @param {string} endpointName the name of an endpoint that controls ready pods.
* @returns {Promise<k8s.V1Pod>}
*/
async #homesteadPod() {
console.log("Attempting to locate homestead pod...");
const namespace = "cattle-system";
const endpointName = "homestead";
async getActivePod(namespace, endpointName) {
console.log(`Attempting to locate ${endpointName} pod...`);
// Loop fetching endpoints, until it matches at least one pod.
/** @type k8s.V1ObjectReference? */
let target = null;
// TODO: switch this to using watch.
for (; ;) {
/** @type k8s.V1EndpointsList */
let endpoints;
({ body: endpoints } = await this.#coreV1API.listNamespacedEndpoints(namespace, { headers: { name: endpointName } }));
console.log("Got homestead endpoints", endpoints);
console.log(`Got ${endpointName} endpoints ${endpoints}`);
target = endpoints?.items?.pop()?.subsets?.pop()?.addresses?.pop()?.targetRef;
console.log("Got homestead target", target);
console.log(`Got ${endpointName} target ${target}`);
if (target) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 500));
}
// Fetch the pod
let { body: pod } = await this.#coreV1API.readNamespacedPod(target.name, target.namespace);
console.log("Got homestead pod", pod);
console.log(`Got ${endpointName} pod ${pod}`);
return pod;
}

/**
* The port that homestead is forwarded on. If unavailable, then a new port
* forward will automatically be created, listening on localhost.
* Create a port forward for an endpoint, listening on localhost.
* Note: currently we can only set up one port fowarding.
* @param {string} namespace The namespace containing the end points to forward to.
* @param {string} endpoint The endpoint to forward to.
* @param {number} port The port to forward.
* @return {Promise<number>} The port number for the port forward.
*/
async homesteadPort() {
async forwardPort(namespace, endpoint, port) {
/** @type net.AddressInfo? */
let address = null;
if (!this.#server) {
Expand All @@ -87,14 +93,16 @@ class KubeClient {
case "EPIPE":
break;
default:
console.log(`Error proxying to homestead:`, error);
console.log(`Error creating proxy:`, error);
}
});
// Find a working homestead pod
let { metadata: { namespace, name: podName } } = await this.#homesteadPod();
this.#forwarder.portForward(namespace, podName, [8443], socket, null, socket)
let pod = await this.getActivePod(namespace, endpoint);
let { metadata: { namespace: podNamespace, name: podName } } = pod;
this.#forwarder.portForward(podNamespace, podName, [port], socket, null, socket)
.catch((e) => {
console.log("Failed to create web socket for fowarding:", e.toString());
socket.destroy(e);
});
});
// Start listening, and block until the listener has been established.
Expand All @@ -119,14 +127,16 @@ class KubeClient {
});
req.on('close', reject);
req.on('error', reject);
// Manually reject on a time out
setTimeout(() => reject(new Error("Timed out making probe connection")), 5000);
});
} catch (e) {
console.log("Error making probe connection", e);
continue;
}
break;
}
console.log("homestead port forwarding is ready");
console.log("Port forwarding is ready");
this.#server = server;
}
address ||= this.#server.address();
Expand Down
29 changes: 23 additions & 6 deletions src/k8s-engine/homestead.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,29 @@

const Helm = require('./helm.js');

// This function ensures that homestead is running in a cluster
// TODO: Handle upgrading homestead
async function ensure() {
/** @type number? */
let homesteadPort = null;

/**
* Ensure that homestead is running in a cluster.
* TODO: Handle upgrading homestead.
* @param {KubeClient} client Connection to Kubernetes (only for port forwarding).
*/
async function ensure(client) {
const namespace = "cattle-system";
const releaseName = "homestead";
// Check if cluster available
// Check if homestead is running
let out;
try {
await Helm.list('cattle-system');
await Helm.list(namespace);
} catch (e) {
// Can't connect to the cluster so there is a problem.
throw new Error(`Unable to connect to cluster ${e}`);
}

try {
out = await Helm.status('homestead', 'cattle-system');
out = await Helm.status(releaseName, namespace);
} catch(e) {
// Couldn't connect to cluster so throw error
if (e.includes("Kubernetes cluster unreachable")) {
Expand All @@ -25,13 +33,22 @@ async function ensure() {

// Homestead isn't installed so install it.
try {
out = await Helm.install('homestead', './resources/homestead-0.0.1.tgz', 'cattle-system', true);
out = await Helm.install(releaseName, './resources/homestead-0.0.1.tgz', namespace, true);
} catch (e2) {
throw new Error(`Unable to install homestead: ${e2}`);
}
}

// Set up port forwarding, without actually using it.
homesteadPort = await client.forwardPort(namespace, "homestead", 8443);
console.log(`Port forward is ready on ${homesteadPort}`);

return out;
}

function getPort() {
return homesteadPort;
}

exports.ensure = ensure;
exports.getPort = getPort;
11 changes: 6 additions & 5 deletions src/k8s-engine/k8s.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ const { KubeClient } = require('./client');
const os = require('os')

const State = {
STOPPED: 0,
STARTING: 1,
STARTED: 2,
STOPPING: 3,
ERROR: 4,
STOPPED: 0, // The engine is not running.
STARTING: 1, // The engine is attempting to start.
STARTED: 2, // The engine is started; the dashboard is not yet ready.
READY: 3, // The engine is started, and the dashboard is ready.
STOPPING: 4, // The engine is attempting to stop.
ERROR: 5, // There is an error and we cannot recover automatically.
}

Object.freeze(State);
Expand Down
Loading

0 comments on commit 2f0b241

Please sign in to comment.