Skip to content

Commit 0e529b2

Browse files
committed
create cluster and scripts for users
1 parent 1a96eef commit 0e529b2

File tree

2 files changed

+148
-25
lines changed

2 files changed

+148
-25
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
your_*.yaml
3-
.env
3+
.env
4+
out

src/cli.js

Lines changed: 146 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
const yargs = require("yargs/yargs");
22
import inquirer from "inquirer";
3+
import { domain } from "process";
34
import { exit } from "yargs";
45
const { hideBin } = require("yargs/helpers");
5-
66
const execa = require("execa");
77

8+
// reading and writing in out/ folder
9+
const fs = require("fs");
10+
811
require("dotenv").config();
912

1013
const runHelperScript = async (filename, params) => {
@@ -23,7 +26,9 @@ const runHelperScript = async (filename, params) => {
2326
};
2427

2528
const generateGoogleClusterCommand = (argv) => {
26-
return `$ gcloud beta container --project "${argv.gcloudProjectId}" \\
29+
// TODO: omit zone if it is intentionally left blank to support regional clusters
30+
// note: this will involve modifying other gcloud commands that mention --zone
31+
return `gcloud beta container --project "${argv.gcloudProjectId}" \\
2732
clusters create "${argv.gcloudClusterName}" \\
2833
--zone "${argv.gcloudClusterZone}" \\
2934
--no-enable-basic-auth \\
@@ -38,20 +43,20 @@ const generateGoogleClusterCommand = (argv) => {
3843
--num-nodes "${argv.gcloudClusterMinNodes}" \\
3944
--enable-stackdriver-kubernetes \\
4045
--enable-ip-alias \\
41-
--network "projects/$PROJECT_ID/global/networks/default" \\
42-
--subnetwork "projects/$PROJECT_ID/regions/${
43-
argv.gcloudClusterZone
44-
}/subnetworks/default" \\
46+
--network "projects/${argv.gcloudProjectId}/global/networks/default" \\
47+
--subnetwork "projects/${argv.gcloudProjectId}/regions/${
48+
argv.gcloudClusterRegion
49+
}/subnetworks/default" \\
4550
--default-max-pods-per-node "110" \\
4651
--addons HorizontalPodAutoscaling,HttpLoadBalancing \\
4752
--enable-autoupgrade \\
4853
--enable-autorepair \\${
49-
argv.gcloudClusterPreemtible ? "\n --preemtible \\" : ""
54+
argv.gcloudClusterPreemptible ? "\n --preemptible \\" : ""
5055
}
5156
--enable-network-policy \\
5257
--enable-autoscaling \\
5358
--min-nodes "${argv.gcloudClusterMinNodes}" \\
54-
--max-nodes "${argv.gcloudClusterMaxNodes}"`;
59+
--max-nodes "${argv.gcloudClusterMaxNodes}"\n`;
5560
};
5661

5762
export async function cli(args) {
@@ -61,6 +66,12 @@ export async function cli(args) {
6166
type: "string",
6267
description: "Method for deploying Coder (gcloud, general-k8s)",
6368
})
69+
.option("save-dir", {
70+
alias: "f",
71+
type: "string",
72+
default: "~/.config/launch-coder",
73+
description: "Path to save config files",
74+
})
6475
.option("domainType", {
6576
alias: "d",
6677
type: "string",
@@ -86,6 +97,10 @@ export async function cli(args) {
8697
type: "string",
8798
default: "coder",
8899
})
100+
.option("gcloud-cluster-region", {
101+
type: "string",
102+
default: "us-central1",
103+
})
89104
.option("gcloud-cluster-zone", {
90105
type: "string",
91106
default: "us-central1-a",
@@ -94,7 +109,7 @@ export async function cli(args) {
94109
type: "string",
95110
default: "e2-highmem-4",
96111
})
97-
.option("gcloud-cluster-preemtible", {
112+
.option("gcloud-cluster-preemptible", {
98113
type: "boolean",
99114
default: true,
100115
})
@@ -110,8 +125,7 @@ export async function cli(args) {
110125
type: "number",
111126
default: 3,
112127
})
113-
// TODO: determine better naming for this:
114-
.option("gcloud-skip-confirm-prompt", {
128+
.option("skip-confirm-prompts", {
115129
type: "boolean",
116130
}).argv;
117131

@@ -147,7 +161,7 @@ export async function cli(args) {
147161
value: "gcloud",
148162
},
149163
{
150-
name: "Install Coder on my current cluster (sketchy)",
164+
name: "Install Coder on my current cluster",
151165
value: "k8s",
152166
},
153167
],
@@ -245,12 +259,13 @@ export async function cli(args) {
245259
let pricing_info = "";
246260

247261
if (
262+
argv.gcloudClusterRegion == "us-central1" &&
248263
argv.gcloudClusterZone == "us-central1-a" &&
249264
argv.gcloudClusterMachineType == "e2-highmem-4" &&
250265
argv.gcloudClusterMinNodes == "1" &&
251266
argv.gcloudClusterMaxNodes == "3" &&
252267
argv.gcloudClusterAutoscaling &&
253-
argv.gcloudClusterPreemtible
268+
argv.gcloudClusterPreemptible
254269
) {
255270
pricing_info =
256271
"This cluster will cost you roughly $40-120/mo to run on Google Cloud depending on usage." +
@@ -275,7 +290,7 @@ export async function cli(args) {
275290

276291
// TODO: impliment ability to edit cluster command in the cli (wohoo)
277292

278-
if (!argv.gcloudSkipConfirmPrompt) {
293+
if (!argv.skipConfirmPrompts) {
279294
const runCommand = await inquirer.prompt({
280295
type: "confirm",
281296
default: true,
@@ -291,17 +306,94 @@ export async function cli(args) {
291306
}
292307
}
293308

294-
const subprocess = execa("ping", ["google.com", "-c", "5"]);
295-
subprocess.stdout.pipe(process.stdout);
296-
const { stdout } = await subprocess;
297-
console.log("WE KNOW THE PROCESS HAS COMPLETED");
309+
// TODO: create different folders for each session
310+
console.log(
311+
"💾 FYI: All of these scripts are being saved in: " + argv.saveDir + "\n"
312+
);
298313

299-
// execa("echo", ["unicorns"]).stdout.pipe(process.stdout);
300-
} else if (argv.method == "k8s") {
301-
console.log("coming sooon moo");
302-
} else {
314+
// switch to the absolute path of the home directory if the user included ~/
315+
if (argv.saveDir.startsWith("~/")) {
316+
const userHome = require("os").homedir();
317+
argv.saveDir = argv.saveDir.replace("~/", userHome + "/");
318+
}
319+
320+
// create our out/ file to hold our creation script, among other things
321+
await execa("mkdir", ["-p", argv.saveDir]).catch((err) => {
322+
console.log(err);
323+
});
324+
325+
// git init (or re-init so the user can easily source-control)
326+
await execa("git", ["init", argv.saveDir]);
327+
328+
// add our lovely script to the out folder
329+
fs.writeFileSync(
330+
argv.saveDir + "/createCluster.sh",
331+
"#!/bin/sh\n" + gCloudCommand
332+
);
333+
await fs.chmodSync(argv.saveDir + "/createCluster.sh", "755");
334+
335+
console.log("\n⏳ Creating your cluster. This will take a few minutes...");
336+
337+
try {
338+
const subprocess = execa("/bin/sh", [argv.saveDir + "/createCluster.sh"]);
339+
subprocess.stdout.pipe(process.stdout);
340+
const { stdout } = await subprocess;
341+
console.log("------------");
342+
console.log(
343+
"✅",
344+
`Cluster "${argv.gcloudClusterName}" has been created!`
345+
);
346+
} catch (err) {
347+
console.log("❌", "Process failed\n\n\n", err.stderr);
348+
return;
349+
}
350+
351+
try {
352+
await execa(
353+
"gcloud",
354+
`container clusters get-credentials ${argv.gcloudClusterName} --zone ${argv.gcloudClusterZone}`.split(
355+
" "
356+
)
357+
);
358+
console.log("✅", "Added to kube context");
359+
} catch (err) {
360+
console.log("❌", "Unable to add to kube context:\n\n\n", err.stderr);
361+
return;
362+
}
363+
364+
// So now we can move on to installing Coder!
365+
}
366+
367+
// if argv.method == "gcloud" at this point
368+
// the script has succeeded in creating the cluster
369+
// and switched context
370+
if (argv.method != "k8s" && argv.method != "gcloud") {
371+
// TODO: standardize these
303372
console.error("Error. Unknown method: " + argv.method);
304373
return;
374+
} else if (argv.method == "k8s") {
375+
// TODO: add checks to ensure the user has a cluster,
376+
// and it has the necessary stuff for Coder
377+
console.log(
378+
"This script does not currently verify that you cluster is ready for Coder yet.\n\nWe recommend checking the docs before continuing:"
379+
);
380+
console.log("\t➡️ https://coder.com/docs/setup/requirements");
381+
382+
if (!argv.skipConfirmPrompts) {
383+
const runCommand = await inquirer.prompt({
384+
type: "confirm",
385+
default: true,
386+
name: "runIt",
387+
message: "Do you to proceed?",
388+
});
389+
390+
if (!runCommand.runIt) {
391+
console.log(
392+
`\nExited. If you have any questions, feel free reach out on Slack:\n\t➡️ https://cdr.co/join-community\n`
393+
);
394+
return 0;
395+
}
396+
}
305397
}
306398

307399
// determine which type of domain to use
@@ -314,16 +406,46 @@ export async function cli(args) {
314406
message: "What type of domain would you like to use?",
315407
choices: [
316408
{
317-
name: `With a free domain from Coder (ex. [myname].${process.env.CLOUDFLARE_DOMAIN})`,
409+
name: `A free domain from Coder (ex. [myname].${process.env.CLOUDFLARE_DOMAIN})`,
318410
value: "auto",
319411
},
320412
{
321-
name: "With a domain name I own on Google CloudDNS",
413+
name: "A domain name I own on Google CloudDNS",
322414
value: "cloud-dns",
323415
},
416+
{
417+
name: "Do not set up a domain for now",
418+
value: "none",
419+
},
324420
],
325421
})),
326422
};
327423
}
424+
425+
// validate domainType
426+
if (argv.domainType == "auto") {
427+
console.log("AUTOMA");
428+
} else if (argv.domainType == "cloud-dns") {
429+
console.log("Well, this is coming soon 💀");
430+
} else if (argv.domainType == "none") {
431+
console.log(
432+
"\nWarning: This means you can't use Coder with DevURLs, a primary way of accessing web services\ninside of a Coder Workspace:\n",
433+
"\t📄 Docs: https://coder.com/docs/environments/devurls\n",
434+
"\t🌎 Alternative: https://ngrok.com/docs (you can nstall this in your images)\n\n"
435+
);
436+
437+
console.log(
438+
"You can always add a domain later, and use a custom provider via our docs."
439+
);
440+
441+
// TODO: add confirmations
442+
} else {
443+
// TODO: standardize these
444+
console.error("Error. Unknown domainType: " + argv.domainType);
445+
return;
446+
}
447+
448+
// install and access Coder
449+
328450
console.log("\n\nat the end with a long argv:", Object.keys(argv).length);
329451
}

0 commit comments

Comments
 (0)