1
1
import * as fs from "fs" ;
2
+ import * as https from "https" ;
2
3
import { join } from "path" ;
3
4
import { exec } from "./util/shell" ;
4
5
import { Werft } from "./util/werft" ;
@@ -16,6 +17,11 @@ const upgrade: string = annotations.upgrade || "false"; // setting to true will
16
17
const skipTests : string = annotations . skipTests || "false" ; // setting to true skips the integration tests
17
18
const deps : string = annotations . deps || "" ; // options: ["external", "internal"] setting to `external` will ensure that all resource dependencies(storage, db, registry) will be external. if unset, a random selection will be used
18
19
20
+ const slackHook : { [ name : string ] : string } = {
21
+ "self-hosted-jobs" : process . env . SH_SLACK_NOTIFICATION_PATH . trim ( ) ,
22
+ "workspace-jobs" : process . env . WS_SLACK_NOTIFICATION_PATH . trim ( ) ,
23
+ "ide-jobs" : process . env . IDE_SLACK_NOTIFICATION_PATH . trim ( ) ,
24
+ }
19
25
20
26
const makefilePath : string = join ( "install/tests" ) ;
21
27
@@ -25,6 +31,7 @@ interface InfraConfig {
25
31
phase : string ;
26
32
makeTarget : string ;
27
33
description : string ;
34
+ slackhook ?: string ;
28
35
}
29
36
30
37
interface TestConfig {
@@ -33,12 +40,14 @@ interface TestConfig {
33
40
CLOUD : string ;
34
41
}
35
42
43
+ const op : string = preview == "true" ? "Preview" : "Test"
44
+
36
45
// Each of the TEST_CONFIGURATIONS define an integration test end-to-end
37
46
// It should be a combination of multiple INFRA_PHASES, order of PHASES slice is important
38
47
const TEST_CONFIGURATIONS : { [ name : string ] : TestConfig } = {
39
48
STANDARD_GKE_TEST : {
40
49
CLOUD : "gcp" ,
41
- DESCRIPTION : "Deploy Gitpod on GKE, with managed DNS, and run integration tests" ,
50
+ DESCRIPTION : ` ${ op } Gitpod on a GKE managed cluster` ,
42
51
PHASES : [
43
52
"STANDARD_GKE_CLUSTER" ,
44
53
"CERT_MANAGER" ,
@@ -51,9 +60,7 @@ const TEST_CONFIGURATIONS: { [name: string]: TestConfig } = {
51
60
} ,
52
61
STANDARD_K3S_TEST : {
53
62
CLOUD : "gcp" , // the cloud provider is still GCP
54
- DESCRIPTION :
55
- "Deploy Gitpod on a K3s cluster, created on a GCP instance," +
56
- " with managed DNS and run integrations tests" ,
63
+ DESCRIPTION : `${ op } Gitpod on a K3s cluster in a GCP instance` ,
57
64
PHASES : [
58
65
"STANDARD_K3S_CLUSTER_ON_GCP" ,
59
66
"CERT_MANAGER" ,
@@ -65,7 +72,7 @@ const TEST_CONFIGURATIONS: { [name: string]: TestConfig } = {
65
72
} ,
66
73
STANDARD_AKS_TEST : {
67
74
CLOUD : "azure" ,
68
- DESCRIPTION : "Deploy Gitpod on AKS, with managed DNS, and run integration tests" ,
75
+ DESCRIPTION : ` ${ op } Gitpod on an AKS managed cluster` ,
69
76
PHASES : [
70
77
"STANDARD_AKS_CLUSTER" ,
71
78
"CERT_MANAGER" ,
@@ -79,7 +86,7 @@ const TEST_CONFIGURATIONS: { [name: string]: TestConfig } = {
79
86
} ,
80
87
STANDARD_EKS_TEST : {
81
88
CLOUD : "aws" ,
82
- DESCRIPTION : "Create an EKS cluster" ,
89
+ DESCRIPTION : ` ${ op } Gitpod on an EKS managed cluster` ,
83
90
PHASES : [
84
91
"STANDARD_EKS_CLUSTER" ,
85
92
"CERT_MANAGER" ,
@@ -189,47 +196,52 @@ const TESTS: { [name: string]: InfraConfig } = {
189
196
WORKSPACE_TEST : {
190
197
phase : "run-workspace-tests" ,
191
198
makeTarget : "run-workspace-tests" ,
192
- description : "Run the test for workspaces" ,
199
+ description : "Run the \`workspace\` tests \`test/tests/workspace\`" ,
200
+ slackhook : slackHook [ "workspace-jobs" ] ,
193
201
} ,
194
202
VSCODE_IDE_TEST : {
195
203
phase : "run-vscode-ide-tests" ,
196
204
makeTarget : "run-vscode-ide-tests" ,
197
- description : "Run the test for vscode IDE" ,
205
+ description : "Run the \`vscode IDE\` tests \`test/tests/ide/vscode\`" ,
206
+ slackhook : slackHook [ "ide-jobs" ] ,
198
207
} ,
199
208
JB_IDE_TEST : {
200
209
phase : "run-jb-ide-tests" ,
201
210
makeTarget : "run-jb-ide-tests" ,
202
- description : "Run the test for jetbrains IDE" ,
211
+ description : "Run the \`jetbrains IDE\` tests \`test/tests/ide/jetbrains\`" ,
212
+ slackhook : slackHook [ "ide-jobs" ] ,
203
213
} ,
204
214
CONTENTSERVICE_TEST : {
205
215
phase : "run-cs-component-tests" ,
206
216
makeTarget : "run-cs-component-tests" ,
207
- description : "Run the test for content-service component " ,
217
+ description : "Run the \`content-service\` tests \`test/tests/components/ content-service\` " ,
208
218
} ,
209
219
DB_TEST : {
210
220
phase : "run-db-component-tests" ,
211
221
makeTarget : "run-db-component-tests" ,
212
- description : "Run the test for database component " ,
222
+ description : "Run the \`database\` tests \`test/tests/components/ database\` " ,
213
223
} ,
214
224
IMAGEBUILDER_TEST : {
215
225
phase : "run-ib-component-tests" ,
216
226
makeTarget : "run-ib-component-tests" ,
217
- description : "Run the test for image-builder component " ,
227
+ description : "Run the \`image-builder\` tests \`test/tests/components/ image-builder\` " ,
218
228
} ,
219
229
SERVER_TEST : {
220
230
phase : "run-server-component-tests" ,
221
231
makeTarget : "run-server-component-tests" ,
222
- description : "Run the test for server component " ,
232
+ description : "Run the \`server\` tests \`test/tests/components/ server\` " ,
223
233
} ,
224
234
WS_DAEMON_TEST : {
225
235
phase : "run-wsd-component-tests" ,
226
236
makeTarget : "run-wsd-component-tests" ,
227
- description : "Run the test for ws-daemon component" ,
237
+ description : "Run the \`ws-daemon\` tests \`test/tests/components/ws-daemon\`" ,
238
+ slackhook : slackHook [ "workspace-jobs" ] ,
228
239
} ,
229
240
WS_MNGR_TEST : {
230
241
phase : "run-wsm-component-tests" ,
231
242
makeTarget : "run-wsm-component-tests" ,
232
- description : "Run the test for ws-manager component" ,
243
+ description : "Run the \`ws-manager\` tests \`test/tests/components/ws-manager\`" ,
244
+ slackhook : slackHook [ "workspace-jobs" ] ,
233
245
} ,
234
246
}
235
247
@@ -246,27 +258,38 @@ installerTests(TEST_CONFIGURATIONS[testConfig]).catch((err) => {
246
258
247
259
export async function installerTests ( config : TestConfig ) {
248
260
console . log ( config . DESCRIPTION ) ;
249
- // these phases set up the infrastructure
261
+
250
262
werft . phase ( `create-${ cloud } -infra` , `Create the infrastructure in ${ cloud } ` ) ;
263
+
251
264
for ( let phase of config . PHASES ) {
252
265
const phaseSteps = INFRA_PHASES [ phase ] ;
253
266
const ret = callMakeTargets ( phaseSteps . phase , phaseSteps . description , phaseSteps . makeTarget ) ;
254
267
if ( ret ) {
255
268
// there is not point in continuing if one stage fails for infra setup
256
- werft . fail ( `create-${ cloud } -infra` , "Cluster creation failed" ) ;
257
- break ;
269
+ const err : Error = new Error ( "Cluster creation failed" )
270
+ werft . fail ( `create-${ cloud } -infra` , err . message ) ;
271
+
272
+ sendFailureSlackAlert ( phaseSteps . description , err , slackHook [ "self-hosted-jobs" ] ) . catch ( ( error : Error ) => {
273
+ console . error ( "Failed to send message to Slack" , error ) ;
274
+ } ) ;
275
+
276
+ throw err
258
277
}
259
278
}
260
279
werft . done ( `create-${ cloud } -infra` ) ;
261
280
262
- if ( upgrade === "true" ) {
281
+ if ( upgrade === "true" ) {
263
282
// we could run integration tests in the current setup
264
283
// but since we run nightly tests on unstable setups, feels unnecessary
265
284
// runIntegrationTests()
266
285
267
286
const upgradePhase = INFRA_PHASES [ "KOTS_UPGRADE" ] ;
268
287
const ret = callMakeTargets ( upgradePhase . phase , upgradePhase . description , upgradePhase . makeTarget ) ;
269
288
if ( ret ) {
289
+ sendFailureSlackAlert ( upgradePhase . description , new Error ( "Upgrade test failed" ) , slackHook [ "self-hosted-jobs" ] ) . catch ( ( error : Error ) => {
290
+ console . error ( "Failed to send message to Slack" , error ) ;
291
+ } ) ;
292
+
270
293
return ;
271
294
}
272
295
}
@@ -289,6 +312,11 @@ if (upgrade === "true") {
289
312
`werft log result -d "self-hosted preview url" url "https://${ process . env [ "TF_VAR_TEST_ID" ] } .tests.gitpod-self-hosted.com"` ,
290
313
) ;
291
314
315
+ sendPreviewSlackAlert ( ) . catch ( ( error : Error ) => {
316
+ console . error ( "Failed to send message to Slack" , error ) ;
317
+ } ) ;
318
+
319
+
292
320
exec ( `werft log result -d "Terraform state" url "Terraform state file name is ${ process . env [ "TF_VAR_TEST_ID" ] } "` ) ;
293
321
294
322
werft . done ( "print-output" ) ;
@@ -302,12 +330,15 @@ function runIntegrationTests() {
302
330
werft . phase ( "run-integration-tests" , "Run all existing integration tests" ) ;
303
331
for ( let test in TESTS ) {
304
332
const testPhase = TESTS [ test ] ;
305
- // todo(nvn): handle the test failures by alerting teams
306
333
const ret = callMakeTargets ( testPhase . phase , testPhase . description , testPhase . makeTarget ) ;
307
334
if ( ret ) {
308
335
exec (
309
336
`werft log result -d "failed test" url "${ testPhase . description } (Phase ${ testPhase . phase } ) failed. Please refer logs."` ,
310
337
) ;
338
+
339
+ sendFailureSlackAlert ( testPhase . description , new Error ( "Integration test failed" ) , testPhase . slackhook ) . catch ( ( error : Error ) => {
340
+ console . error ( "Failed to send message to Slack" , error ) ;
341
+ } ) ;
311
342
}
312
343
}
313
344
@@ -383,3 +414,120 @@ function cleanup() {
383
414
384
415
return ret ;
385
416
}
417
+
418
+ export async function sendFailureSlackAlert ( phase : string , err : Error , hook : string ) : Promise < void > {
419
+ if ( typeof hook === 'undefined' || hook === null ) {
420
+ return
421
+ }
422
+
423
+ const repo = context . Repository . host + "/" + context . Repository . owner + "/" + context . Repository . repo ;
424
+ const data = JSON . stringify ( {
425
+ blocks : [
426
+ {
427
+ type : "section" ,
428
+ text : {
429
+ type : "mrkdwn" ,
430
+ text : ":X: *self-hosted test failure*\n_Test configuration:_ `" + config . DESCRIPTION + "`\n_Build:_ `" + context . Name + "`" ,
431
+ } ,
432
+ accessory : {
433
+ type : "button" ,
434
+ text : {
435
+ type : "plain_text" ,
436
+ text : "Go to Werft" ,
437
+ emoji : true
438
+ } ,
439
+ value : "click_me_123" ,
440
+ url : "https://werft.gitpod-dev.com/job/" + context . Name ,
441
+ action_id : "button-action"
442
+ }
443
+ } ,
444
+ {
445
+ type : "section" ,
446
+ fields : [
447
+ {
448
+ type : "mrkdwn" ,
449
+ text : "*Failed step:*\n" + phase + "\n" ,
450
+ } ,
451
+ {
452
+ type : "mrkdwn" ,
453
+ text : "*Error:*\n" + err + "\n" ,
454
+ } ,
455
+ ]
456
+ } ,
457
+ ] ,
458
+ } ) ;
459
+ return new Promise ( ( resolve , reject ) => {
460
+ const req = https . request (
461
+ {
462
+ hostname : "hooks.slack.com" ,
463
+ port : 443 ,
464
+ path : hook ,
465
+ method : "POST" ,
466
+ headers : {
467
+ "Content-Type" : "application/json" ,
468
+ "Content-Length" : data . length ,
469
+ } ,
470
+ } ,
471
+ ( ) => resolve ( ) ,
472
+ ) ;
473
+ req . on ( "error" , ( error : Error ) => reject ( error ) ) ;
474
+ req . write ( data ) ;
475
+ req . end ( ) ;
476
+ } ) ;
477
+ }
478
+
479
+ export async function sendPreviewSlackAlert ( ) : Promise < void > {
480
+ const data = JSON . stringify ( {
481
+ blocks : [
482
+ {
483
+ type : "section" ,
484
+ text : {
485
+ type : "mrkdwn" ,
486
+ text : ":white_check_mark: *self-hosted preview environment\n_Test configuration:_ `" + config . DESCRIPTION + "`*\n_Build:_ `" + context . Name + "`" ,
487
+ } ,
488
+ accessory : {
489
+ type : "button" ,
490
+ text : {
491
+ type : "plain_text" ,
492
+ text : "Go to Werft" ,
493
+ emoji : true
494
+ } ,
495
+ value : "click_me_123" ,
496
+ url : "https://werft.gitpod-dev.com/job/" + context . Name ,
497
+ action_id : "button-action"
498
+ }
499
+ } ,
500
+ {
501
+ type : "section" ,
502
+ fields : [
503
+ {
504
+ type : "mrkdwn" ,
505
+ text : "*URL:*\n<https://" + process . env [ "TF_VAR_TEST_ID" ] + ".tests.gitpod-self-hosted.com|Access preview setup>" ,
506
+ } ,
507
+ {
508
+ type : "mrkdwn" ,
509
+ text : "*Terraform workspace:*\n`" + process . env [ "TF_VAR_TEST_ID" ] + "`\n" ,
510
+ } ,
511
+ ]
512
+ } ,
513
+ ] ,
514
+ } ) ;
515
+ return new Promise ( ( resolve , reject ) => {
516
+ const req = https . request (
517
+ {
518
+ hostname : "hooks.slack.com" ,
519
+ port : 443 ,
520
+ path : process . env . SH_SLACK_NOTIFICATION_PATH . trim ( ) ,
521
+ method : "POST" ,
522
+ headers : {
523
+ "Content-Type" : "application/json" ,
524
+ "Content-Length" : data . length ,
525
+ } ,
526
+ } ,
527
+ ( ) => resolve ( ) ,
528
+ ) ;
529
+ req . on ( "error" , ( error : Error ) => reject ( error ) ) ;
530
+ req . write ( data ) ;
531
+ req . end ( ) ;
532
+ } ) ;
533
+ }
0 commit comments