@@ -11,6 +11,7 @@ import (
11
11
"errors"
12
12
"fmt"
13
13
"net/url"
14
+ "strconv"
14
15
"strings"
15
16
"time"
16
17
@@ -65,16 +66,27 @@ type ComponentAPI struct {
65
66
BlobServiceClient csapi.BlobServiceClient
66
67
}
67
68
dbStatus struct {
68
- Port int
69
- Password string
70
- DB * sql.DB
69
+ Config * DBConfig
70
+ DB * sql.DB
71
71
}
72
72
imgbldStatus struct {
73
73
Port int
74
74
Client imgbldr.ImageBuilderClient
75
75
}
76
76
}
77
77
78
+ type DBConfig struct {
79
+ Host string
80
+ Port int32
81
+ ForwardPort * ForwardPort
82
+ Password string
83
+ }
84
+
85
+ type ForwardPort struct {
86
+ PodName string
87
+ RemotePort int32
88
+ }
89
+
78
90
// Supervisor provides a gRPC connection to a workspace's supervisor
79
91
func (c * ComponentAPI ) Supervisor (instanceID string ) (res grpc.ClientConnInterface ) {
80
92
pod , _ , err := c .t .selectPod (ComponentWorkspace , selectPodOptions {InstanceID : instanceID })
@@ -370,81 +382,31 @@ func (c *ComponentAPI) DB() *sql.DB {
370
382
c .t .t .Fatalf ("cannot access database: %q" , rerr )
371
383
}()
372
384
373
- if c .dbStatus .Port == 0 {
374
- svc , err := c .t .clientset .CoreV1 ().Services (c .t .namespace ).Get (context .Background (), "db" , metav1.GetOptions {})
375
- if err != nil {
376
- rerr = err
377
- return nil
378
- }
379
- pods , err := c .t .clientset .CoreV1 ().Pods (c .t .namespace ).List (context .Background (), metav1.ListOptions {
380
- LabelSelector : labels .SelectorFromSet (svc .Spec .Selector ).String (),
381
- })
385
+ if c .dbStatus .Config == nil {
386
+ config , err := c .findDBConfig ()
382
387
if err != nil {
383
388
rerr = err
384
389
return nil
385
390
}
386
- if len (pods .Items ) == 0 {
387
- rerr = xerrors .Errorf ("no pods for service %s found" , svc .Name )
388
- return nil
389
- }
390
- var pod * corev1.Pod
391
- for _ , p := range pods .Items {
392
- if p .Spec .NodeName == "" {
393
- // no node means the pod can't be ready
394
- continue
395
- }
396
- var isReady bool
397
- for _ , cond := range p .Status .Conditions {
398
- if cond .Type == corev1 .PodReady {
399
- isReady = cond .Status == corev1 .ConditionTrue
400
- break
401
- }
402
- }
403
- if ! isReady {
404
- continue
405
- }
406
-
407
- pod = & p
408
- break
409
- }
410
- if pod == nil {
411
- rerr = xerrors .Errorf ("no active pod for service %s found" , svc .Name )
412
- return nil
413
- }
391
+ c .dbStatus .Config = config
392
+ }
393
+ config := c .dbStatus .Config
414
394
415
- localPort , err := getFreePort ()
416
- if err != nil {
417
- rerr = err
418
- return nil
419
- }
395
+ // if configured: setup local port-forward to DB pod
396
+ if config .ForwardPort != nil {
420
397
ctx , cancel := context .WithCancel (context .Background ())
421
- ready , errc := forwardPort (ctx , c .t .restConfig , c .t .namespace , pod . Name , fmt .Sprintf ("%d:3306 " , localPort ))
398
+ ready , errc := forwardPort (ctx , c .t .restConfig , c .t .namespace , config . ForwardPort . PodName , fmt .Sprintf ("%d:%d " , config . Port , config . ForwardPort . RemotePort ))
422
399
select {
423
- case err = <- errc :
400
+ case err : = <- errc :
424
401
cancel ()
425
402
rerr = err
426
403
return nil
427
404
case <- ready :
428
405
}
429
406
c .t .closer = append (c .t .closer , func () error { cancel (); return nil })
430
-
431
- c .dbStatus .Port = localPort
432
- }
433
- if c .dbStatus .Password == "" {
434
- sct , err := c .t .clientset .CoreV1 ().Secrets (c .t .namespace ).Get (context .Background (), "db-password" , metav1.GetOptions {})
435
- if err != nil {
436
- rerr = err
437
- return nil
438
- }
439
- pwd , ok := sct .Data ["mysql-root-password" ]
440
- if ! ok {
441
- rerr = xerrors .Errorf ("no mysql-root-password data present in secret %s" , sct .Name )
442
- return nil
443
- }
444
- c .dbStatus .Password = string (pwd )
445
407
}
446
408
447
- db , err := sql .Open ("mysql" , fmt .Sprintf ("gitpod:%s@tcp(127.0.0.1 :%d)/gitpod" , c . dbStatus . Password , c . dbStatus .Port ))
409
+ db , err := sql .Open ("mysql" , fmt .Sprintf ("gitpod:%s@tcp(%s :%d)/gitpod" , config . Password , config . Host , config .Port ))
448
410
if err != nil {
449
411
rerr = err
450
412
return nil
@@ -455,6 +417,127 @@ func (c *ComponentAPI) DB() *sql.DB {
455
417
return db
456
418
}
457
419
420
+ func (c * ComponentAPI ) findDBConfig () (* DBConfig , error ) {
421
+ config , err := c .findDBConfigFromPodEnv ("server" )
422
+ if err != nil {
423
+ return nil , err
424
+ }
425
+
426
+ // here we _assume_ that "config" points to a service: find us a concrete DB pod to forward to
427
+ svc , err := c .t .clientset .CoreV1 ().Services (c .t .namespace ).Get (context .Background (), config .Host , metav1.GetOptions {})
428
+ if err != nil {
429
+ return nil , err
430
+ }
431
+
432
+ // find remotePort
433
+ var remotePort int32
434
+ for _ , p := range svc .Spec .Ports {
435
+ if p .Port == config .Port {
436
+ remotePort = p .TargetPort .IntVal
437
+ if remotePort == 0 {
438
+ remotePort = p .Port
439
+ }
440
+ break
441
+ }
442
+ }
443
+ if remotePort == 0 {
444
+ return nil , fmt .Errorf ("no ports found on service: %s" , svc .Name )
445
+ }
446
+
447
+ // find pod to forward to
448
+ pods , err := c .t .clientset .CoreV1 ().Pods (c .t .namespace ).List (context .Background (), metav1.ListOptions {
449
+ LabelSelector : labels .SelectorFromSet (svc .Spec .Selector ).String (),
450
+ })
451
+ if err != nil {
452
+ return nil , err
453
+ }
454
+ if len (pods .Items ) == 0 {
455
+ return nil , fmt .Errorf ("no pods for service %s found" , svc .Name )
456
+ }
457
+ var pod * corev1.Pod
458
+ for _ , p := range pods .Items {
459
+ if p .Spec .NodeName == "" {
460
+ // no node means the pod can't be ready
461
+ continue
462
+ }
463
+ var isReady bool
464
+ for _ , cond := range p .Status .Conditions {
465
+ if cond .Type == corev1 .PodReady {
466
+ isReady = cond .Status == corev1 .ConditionTrue
467
+ break
468
+ }
469
+ }
470
+ if ! isReady {
471
+ continue
472
+ }
473
+
474
+ pod = & p
475
+ break
476
+ }
477
+ if pod == nil {
478
+ return nil , fmt .Errorf ("no active pod for service %s found" , svc .Name )
479
+ }
480
+
481
+ localPort , err := getFreePort ()
482
+ if err != nil {
483
+ return nil , err
484
+ }
485
+ config .Port = int32 (localPort )
486
+ config .ForwardPort = & ForwardPort {
487
+ RemotePort : remotePort ,
488
+ PodName : pod .Name ,
489
+ }
490
+ config .Host = "127.0.0.1"
491
+
492
+ return config , nil
493
+ }
494
+
495
+ func (c * ComponentAPI ) findDBConfigFromPodEnv (componentName string ) (* DBConfig , error ) {
496
+ lblSelector := fmt .Sprintf ("component=%s" , componentName )
497
+ list , err := c .t .clientset .CoreV1 ().Pods (c .t .namespace ).List (context .Background (), metav1.ListOptions {
498
+ LabelSelector : lblSelector ,
499
+ })
500
+ if err != nil {
501
+ return nil , err
502
+ }
503
+ if len (list .Items ) == 0 {
504
+ return nil , fmt .Errorf ("no pods found for: %s" , lblSelector )
505
+ }
506
+ pod := list .Items [0 ]
507
+
508
+ var password string
509
+ var port int32
510
+ var host string
511
+ OuterLoop:
512
+ for _ , c := range pod .Spec .Containers {
513
+ for _ , v := range c .Env {
514
+ if v .Name == "DB_PASSWORD" {
515
+ password = v .Value
516
+ } else if v .Name == "DB_PORT" {
517
+ pPort , err := strconv .Atoi (v .Value )
518
+ if err != nil {
519
+ return nil , fmt .Errorf ("error parsing DB_PORT '%s' on pod %s!" , v .Value , pod .Name )
520
+ }
521
+ port = int32 (pPort )
522
+ } else if v .Name == "DB_HOST" {
523
+ host = v .Value
524
+ }
525
+ if password != "" && port != 0 && host != "" {
526
+ break OuterLoop
527
+ }
528
+ }
529
+ }
530
+ if password == "" || port == 0 || host == "" {
531
+ return nil , fmt .Errorf ("could not find complete DBConfig on pod %s!" , pod .Name )
532
+ }
533
+ config := DBConfig {
534
+ Host : host ,
535
+ Port : port ,
536
+ Password : password ,
537
+ }
538
+ return & config , nil
539
+ }
540
+
458
541
// ImageBuilder provides access to the image builder service.
459
542
func (c * ComponentAPI ) ImageBuilder () imgbldr.ImageBuilderClient {
460
543
if c .imgbldStatus .Client != nil {
0 commit comments