1
1
const yargs = require ( "yargs/yargs" ) ;
2
2
import inquirer from "inquirer" ;
3
+ import { exit } from "yargs" ;
3
4
const { hideBin } = require ( "yargs/helpers" ) ;
4
5
5
6
const execa = require ( "execa" ) ;
6
7
7
8
require ( "dotenv" ) . config ( ) ;
8
9
10
+ const runHelperScript = async ( filename , params ) => {
11
+ try {
12
+ let run = await execa ( "/bin/sh" , [
13
+ __dirname + `/../shell-helpers/${ filename } .sh` ,
14
+ ] ) ;
15
+
16
+ if ( run && run . stdout ) {
17
+ return run . stdout ;
18
+ }
19
+ } catch ( err ) {
20
+ throw err ;
21
+ return ;
22
+ }
23
+ } ;
24
+
25
+ const generateGoogleClusterCommand = ( argv ) => {
26
+ return `$ gcloud beta container --project "${ argv . gcloudProjectId } " \\
27
+ clusters create "${ argv . gcloudClusterName } " \\
28
+ --zone "${ argv . gcloudClusterZone } " \\
29
+ --no-enable-basic-auth \\
30
+ --node-version "latest" \\
31
+ --cluster-version "latest" \\
32
+ --machine-type "${ argv . gcloudClusterMachineType } " \\
33
+ --image-type "UBUNTU" \\
34
+ --disk-type "pd-standard" \\
35
+ --disk-size "50" \\
36
+ --metadata disable-legacy-endpoints=true \\
37
+ --scopes "https://www.googleapis.com/auth/cloud-platform" \\
38
+ --num-nodes "${ argv . gcloudClusterMinNodes } " \\
39
+ --enable-stackdriver-kubernetes \\
40
+ --enable-ip-alias \\
41
+ --network "projects/$PROJECT_ID/global/networks/default" \\
42
+ --subnetwork "projects/$PROJECT_ID/regions/${
43
+ argv . gcloudClusterZone
44
+ } /subnetworks/default" \\
45
+ --default-max-pods-per-node "110" \\
46
+ --addons HorizontalPodAutoscaling,HttpLoadBalancing \\
47
+ --enable-autoupgrade \\
48
+ --enable-autorepair \\${
49
+ argv . gcloudClusterPreemtible ? "\n --preemtible \\" : ""
50
+ }
51
+ --enable-network-policy \\
52
+ --enable-autoscaling \\
53
+ --min-nodes "${ argv . gcloudClusterMinNodes } " \\
54
+ --max-nodes "${ argv . gcloudClusterMaxNodes } "` ;
55
+ } ;
56
+
9
57
export async function cli ( args ) {
10
58
let argv = yargs ( hideBin ( args ) )
11
59
. option ( "method" , {
@@ -30,30 +78,61 @@ export async function cli(args) {
30
78
type : "string" ,
31
79
alias : "n" ,
32
80
description : "Name for Coder subdomain" ,
81
+ } )
82
+ . option ( "gcloud-project-id" , {
83
+ type : "string" ,
84
+ } )
85
+ . option ( "gcloud-cluster-name" , {
86
+ type : "string" ,
87
+ default : "coder" ,
88
+ } )
89
+ . option ( "gcloud-cluster-zone" , {
90
+ type : "string" ,
91
+ default : "us-central1-a" ,
92
+ } )
93
+ . option ( "gcloud-cluster-machine-type" , {
94
+ type : "string" ,
95
+ default : "e2-highmem-4" ,
96
+ } )
97
+ . option ( "gcloud-cluster-preemtible" , {
98
+ type : "boolean" ,
99
+ default : true ,
100
+ } )
101
+ . option ( "gcloud-cluster-autoscaling" , {
102
+ type : "boolean" ,
103
+ default : true ,
104
+ } )
105
+ . option ( "gcloud-cluster-min-nodes" , {
106
+ type : "number" ,
107
+ default : 1 ,
108
+ } )
109
+ . option ( "gcloud-cluster-max-nodes" , {
110
+ type : "number" ,
111
+ default : 3 ,
112
+ } )
113
+ // TODO: determine better naming for this:
114
+ . option ( "gcloud-skip-confirm-prompt" , {
115
+ type : "boolean" ,
33
116
} ) . argv ;
34
117
35
118
// detect if we are on google cloud :)
36
119
37
- const checkGoogleCloud = await execa ( "/bin/sh" , [
38
- // probably a silly way to do so, considering I can also ping in node
39
- // oh well. hackathon
40
- __dirname + "/../shell-helpers/detectGoogleCloud.sh" ,
41
- ] ) ;
120
+ const checkGoogleCloud = await runHelperScript ( "detectGoogleCloud" ) ;
121
+
42
122
if ( ! argv . method && checkGoogleCloud && checkGoogleCloud . stdout == "true" ) {
43
123
console . log (
44
124
"Auto-detected you are on Google Cloud, so we'll deploy there 🚀\nYou can manually change this by executing with --method"
45
125
) ;
46
126
} else if ( argv . method == undefined ) {
47
- console . log ( "YEP IT IS IN he" , argv . method ) ;
48
127
argv = {
49
128
...argv ,
50
129
...( await inquirer . prompt ( {
51
130
type : "list" ,
52
131
name : "method" ,
53
- message : "Where would you like to deploy Coder" ,
132
+ message : "Where would you like to deploy Coder? " ,
54
133
choices : [
55
134
{
56
- name : `Create a fresh Google Cloud cluster for me! ` ,
135
+ name : `Create a fresh Google Cloud cluster for me and install Coder ` ,
57
136
value : "gcloud" ,
58
137
} ,
59
138
{
@@ -65,6 +144,156 @@ export async function cli(args) {
65
144
} ;
66
145
}
67
146
147
+ if ( argv . method == "gcloud" ) {
148
+ // ensure gcloud-cli is installed and active
149
+
150
+ // TODO: add better user education on what the prereqs are
151
+ console . log ( "Checking for prerequisites..." ) ;
152
+ try {
153
+ await runHelperScript ( "googleCloudPrereqs" ) ;
154
+ console . log ( "✅" , "You seem to have all the dependencies installed!" ) ;
155
+ } catch ( err ) {
156
+ console . log ( "❌" , err . stderr ) ;
157
+ return ;
158
+ }
159
+
160
+ if ( ! argv . gcloudProjectId ) {
161
+ let defaultProject = false ;
162
+ let projects = [ ] ;
163
+
164
+ // try to get the default project
165
+ try {
166
+ const listOfProjects = await runHelperScript (
167
+ "googleCloudDefaultProject"
168
+ ) ;
169
+
170
+ defaultProject = await runHelperScript ( "googleCloudDefaultProject" ) ;
171
+ const projectsJson = await runHelperScript ( "googleCloudProjects" ) ;
172
+ projects = JSON . parse ( projectsJson ) . map ( ( project ) => {
173
+ return project . projectId ;
174
+ } ) ;
175
+
176
+ // ensure we are actually fetching IDs
177
+ if ( projects [ 0 ] == undefined ) {
178
+ throw "could not read project ID" ;
179
+ }
180
+
181
+ console . log ( "📄 Got a list of your Google Cloud projects!\n" ) ;
182
+ } catch ( err ) {
183
+ // reset projects list
184
+ projects = [ ] ;
185
+
186
+ // TODO: ensure it is actually no biggie
187
+ console . log ( "Ran into an error fetching your projects... No biggie 🙂" ) ;
188
+ }
189
+
190
+ // show a select field if we found a list
191
+ if ( projects . length ) {
192
+ argv = {
193
+ ...argv ,
194
+ ...( await inquirer . prompt ( {
195
+ type : "list" ,
196
+ name : "gcloudProjectId" ,
197
+ default : defaultProject ,
198
+ message : `Google Cloud Project:` ,
199
+ validate : ( that ) => {
200
+ // TODO: validate this project actually exists
201
+ return that != "" ;
202
+ } ,
203
+ choices : projects ,
204
+ } ) ) ,
205
+ } ;
206
+ } else
207
+ argv = {
208
+ ...argv ,
209
+ ...( await inquirer . prompt ( {
210
+ type : "input" ,
211
+ name : "gcloudProjectId" ,
212
+ default : undefined ,
213
+ message : `Google Cloud Project:` ,
214
+ validate : ( that ) => {
215
+ // TODO: validate this project actually exists
216
+ return that != "" ;
217
+ } ,
218
+ choices : [
219
+ {
220
+ name : `Create a fresh Google Cloud cluster for me and install Coder` ,
221
+ value : "gcloud" ,
222
+ } ,
223
+ {
224
+ name : "Install Coder on my current cluster (sketchy)" ,
225
+ value : "k8s" ,
226
+ } ,
227
+ ] ,
228
+ } ) ) ,
229
+ } ;
230
+ }
231
+
232
+ let gCloudCommand = generateGoogleClusterCommand ( argv ) ;
233
+
234
+ // TODO: impliment pricing calculations with Google API
235
+ let pricing_info = "" ;
236
+
237
+ if (
238
+ argv . gcloudClusterZone == "us-central1-a" &&
239
+ argv . gcloudClusterMachineType == "e2-highmem-4" &&
240
+ argv . gcloudClusterMinNodes == "1" &&
241
+ argv . gcloudClusterMaxNodes == "3" &&
242
+ argv . gcloudClusterAutoscaling &&
243
+ argv . gcloudClusterPreemtible
244
+ ) {
245
+ pricing_info =
246
+ "This cluster will cost you roughly $40-120/mo to run on Google Cloud depending on usage." +
247
+ "\n\nNote: this is just an estimate, we recommend researching yourself and monitoring billing:" ;
248
+ } else {
249
+ pricing_info =
250
+ "You are not using default settings. Be sure to calculate the pricing info for your cluster" ;
251
+ }
252
+ console . log (
253
+ "\n💻 Your command is:" ,
254
+ "\n------------\n" ,
255
+
256
+ gCloudCommand ,
257
+ "\n------------" ,
258
+ "\n\n💵 " + pricing_info + "\n" ,
259
+ "\t➡️ GKE Pricing: https://cloud.google.com/kubernetes-engine/pricing\n" ,
260
+ "\t➡️ Storage pricing: https://cloud.google.com/compute/disks-image-pricing\n" ,
261
+ "\t➡️ Machine pricing: https://cloud.google.com/compute/all-pricing\n\n" ,
262
+ "\t➡️ or use the Google Cloud Pricing Calculator: https://cloud.google.com/products/calculator\n" ,
263
+ "\n------------"
264
+ ) ;
265
+
266
+ // TODO: impliment ability to edit cluster command in the cli (wohoo)
267
+
268
+ if ( ! argv . gcloudSkipConfirmPrompt ) {
269
+ const runCommand = await inquirer . prompt ( {
270
+ type : "confirm" ,
271
+ default : true ,
272
+ name : "runIt" ,
273
+ message : "Do you want to run this command?" ,
274
+ } ) ;
275
+
276
+ if ( ! runCommand . runIt ) {
277
+ console . log (
278
+ `\n\nOk :) Feel free to modify the command as needed, run it yourself, then you can run "launch-coder --mode k8s" to install Coder on the cluster you manually created`
279
+ ) ;
280
+ return 0 ;
281
+ }
282
+ }
283
+
284
+ const subprocess = execa ( "ping" , [ "google.com" , "-c" , "5" ] ) ;
285
+ subprocess . stdout . pipe ( process . stdout ) ;
286
+ const { stdout } = await subprocess ;
287
+ console . log ( "WE KNOW THE PROCESS HAS COMPLETED" ) ;
288
+
289
+ // execa("echo", ["unicorns"]).stdout.pipe(process.stdout);
290
+ } else if ( argv . method == "k8s" ) {
291
+ console . log ( "coming sooon moo" ) ;
292
+ } else {
293
+ console . error ( "Error. Unknown method: " + argv . method ) ;
294
+ return ;
295
+ }
296
+
68
297
// determine which type of domain to use
69
298
if ( ! argv . domainType ) {
70
299
argv = {
@@ -86,5 +315,5 @@ export async function cli(args) {
86
315
} ) ) ,
87
316
} ;
88
317
}
89
- console . log ( "epic answer dood " , argv ) ;
318
+ console . log ( "\n\nat the end with a long argv: " , Object . keys ( argv ) . length ) ;
90
319
}
0 commit comments