5
5
package main
6
6
7
7
import (
8
- "bytes"
9
8
"context"
10
9
"encoding/json"
11
10
"errors"
12
11
"fmt"
13
- "io"
14
12
"io/ioutil"
15
13
"net"
16
14
"net/http"
@@ -20,13 +18,13 @@ import (
20
18
"os/signal"
21
19
"path/filepath"
22
20
"reflect"
23
- "regexp"
24
21
"strconv"
25
22
"strings"
26
23
"syscall"
27
24
"time"
28
25
29
26
"github.com/hashicorp/go-version"
27
+ cp "github.com/otiai10/copy"
30
28
"golang.org/x/xerrors"
31
29
"google.golang.org/grpc"
32
30
"google.golang.org/grpc/credentials/insecure"
50
48
const BackendPath = "/ide-desktop/backend"
51
49
const ProductInfoPath = BackendPath + "/product-info.json"
52
50
51
+ type LaunchContext struct {
52
+ alias string
53
+ projectDir string
54
+ configDir string
55
+ systemDir string
56
+ projectConfigDir string
57
+ wsInfo * supervisor.WorkspaceInfoResponse
58
+ }
59
+
53
60
// JB startup entrypoint
54
61
func main () {
55
62
log .Init (ServiceName , Version , true , false )
@@ -65,7 +72,13 @@ func main() {
65
72
label = os .Args [3 ]
66
73
}
67
74
68
- backendVersion , err := resolveBackendVersion ()
75
+ info , err := resolveProductInfo ()
76
+ if err != nil {
77
+ log .WithError (err ).Error ("failed to resolve product info" )
78
+ return
79
+ }
80
+
81
+ backendVersion , err := version .NewVersion (info .Version )
69
82
if err != nil {
70
83
log .WithError (err ).Error ("failed to resolve backend version" )
71
84
return
@@ -77,8 +90,8 @@ func main() {
77
90
return
78
91
}
79
92
80
- repoRoot := wsInfo .GetCheckoutLocation ()
81
- gitpodConfig , err := parseGitpodConfig (repoRoot )
93
+ projectDir := wsInfo .GetCheckoutLocation ()
94
+ gitpodConfig , err := parseGitpodConfig (projectDir )
82
95
if err != nil {
83
96
log .WithError (err ).Error ("failed to parse .gitpod.yml" )
84
97
}
@@ -95,10 +108,33 @@ func main() {
95
108
log .WithError (err ).Error ("failed to configure vmoptions" )
96
109
}
97
110
111
+ // Set default config and system directories under /workspace to preserve between restarts
112
+ qualifier := os .Getenv ("JETBRAINS_BACKEND_QUALIFIER" )
113
+ if qualifier == "stable" {
114
+ qualifier = ""
115
+ } else {
116
+ qualifier = "-" + qualifier
117
+ }
118
+ configDir := fmt .Sprintf ("/workspace/.config/JetBrains%s" , qualifier )
119
+ launchCtx := & LaunchContext {
120
+ alias : alias ,
121
+ wsInfo : wsInfo ,
122
+ projectDir : projectDir ,
123
+ configDir : configDir ,
124
+ systemDir : fmt .Sprintf ("/workspace/.cache/JetBrains%s" , qualifier ),
125
+ projectConfigDir : fmt .Sprintf ("%s/RemoteDev-%s/%s" , configDir , info .ProductCode , strings .ReplaceAll (projectDir , "/" , "_" )),
126
+ }
127
+
128
+ // sync initial options
129
+ err = syncOptions (launchCtx )
130
+ if err != nil {
131
+ log .WithError (err ).Error ("failed to sync initial options" )
132
+ }
133
+
98
134
// install project plugins
99
135
version_2022_1 , _ := version .NewVersion ("2022.1" )
100
136
if version_2022_1 .LessThanOrEqual (backendVersion ) {
101
- err = installPlugins (repoRoot , gitpodConfig , alias )
137
+ err = installPlugins (gitpodConfig , launchCtx )
102
138
installPluginsCost := time .Now ().Local ().Sub (startTime ).Milliseconds ()
103
139
if err != nil {
104
140
log .WithError (err ).WithField ("cost" , installPluginsCost ).Error ("installing repo plugins: done" )
@@ -112,7 +148,7 @@ func main() {
112
148
if err != nil {
113
149
log .WithError (err ).Error ("failed to install gitpod-remote plugin" )
114
150
}
115
- go run (wsInfo , alias )
151
+ go run (launchCtx )
116
152
117
153
debugAgentPrefix := "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:"
118
154
http .HandleFunc ("/debug" , func (w http.ResponseWriter , r * http.Request ) {
@@ -324,13 +360,13 @@ func resolveWorkspaceInfo(ctx context.Context) (*supervisor.WorkspaceInfoRespons
324
360
return nil , errors .New ("failed with attempt 10 times" )
325
361
}
326
362
327
- func run (wsInfo * supervisor. WorkspaceInfoResponse , alias string ) {
363
+ func run (launchCtx * LaunchContext ) {
328
364
var args []string
329
365
args = append (args , "run" )
330
- args = append (args , wsInfo . GetCheckoutLocation () )
331
- cmd := remoteDevServerCmd (args )
332
- cmd .Env = append (cmd .Env , "JETBRAINS_GITPOD_BACKEND_KIND=" + alias )
333
- workspaceUrl , err := url .Parse (wsInfo .WorkspaceUrl )
366
+ args = append (args , launchCtx . projectDir )
367
+ cmd := remoteDevServerCmd (args , launchCtx )
368
+ cmd .Env = append (cmd .Env , "JETBRAINS_GITPOD_BACKEND_KIND=" + launchCtx . alias )
369
+ workspaceUrl , err := url .Parse (launchCtx . wsInfo .WorkspaceUrl )
334
370
if err == nil {
335
371
cmd .Env = append (cmd .Env , "JETBRAINS_GITPOD_WORKSPACE_HOST=" + workspaceUrl .Hostname ())
336
372
}
@@ -342,7 +378,7 @@ func run(wsInfo *supervisor.WorkspaceInfoResponse, alias string) {
342
378
}
343
379
344
380
// Nicely handle SIGTERM sinal
345
- go handleSignal (wsInfo . GetCheckoutLocation () )
381
+ go handleSignal ()
346
382
347
383
if err := cmd .Wait (); err != nil {
348
384
log .WithError (err ).Error ("failed to wait" )
@@ -351,28 +387,22 @@ func run(wsInfo *supervisor.WorkspaceInfoResponse, alias string) {
351
387
os .Exit (cmd .ProcessState .ExitCode ())
352
388
}
353
389
354
- func remoteDevServerCmd (args []string ) * exec.Cmd {
390
+ func remoteDevServerCmd (args []string , productContext * LaunchContext ) * exec.Cmd {
355
391
cmd := exec .Command (BackendPath + "/bin/remote-dev-server.sh" , args ... )
356
392
cmd .Env = os .Environ ()
357
393
358
394
// Set default config and system directories under /workspace to preserve between restarts
359
- qualifier := os .Getenv ("JETBRAINS_BACKEND_QUALIFIER" )
360
- if qualifier == "stable" {
361
- qualifier = ""
362
- } else {
363
- qualifier = "-" + qualifier
364
- }
365
395
cmd .Env = append (cmd .Env ,
366
- fmt .Sprintf ("IJ_HOST_CONFIG_BASE_DIR=/workspace/.config/JetBrains %s" , qualifier ),
367
- fmt .Sprintf ("IJ_HOST_SYSTEM_BASE_DIR=/workspace/.cache/JetBrains %s" , qualifier ),
396
+ fmt .Sprintf ("IJ_HOST_CONFIG_BASE_DIR=%s" , productContext . configDir ),
397
+ fmt .Sprintf ("IJ_HOST_SYSTEM_BASE_DIR=%s" , productContext . systemDir ),
368
398
)
369
399
370
400
cmd .Stderr = os .Stderr
371
401
cmd .Stdout = os .Stdout
372
402
return cmd
373
403
}
374
404
375
- func handleSignal (projectPath string ) {
405
+ func handleSignal () {
376
406
sigChan := make (chan os.Signal , 1 )
377
407
signal .Notify (sigChan , os .Interrupt , syscall .SIGTERM )
378
408
@@ -488,10 +518,11 @@ func updateVMOptions(
488
518
}
489
519
*/
490
520
type ProductInfo struct {
491
- Version string `json:"version"`
521
+ Version string `json:"version"`
522
+ ProductCode string `json:"productCode"`
492
523
}
493
524
494
- func resolveBackendVersion () (* version. Version , error ) {
525
+ func resolveProductInfo () (* ProductInfo , error ) {
495
526
f , err := os .Open (ProductInfoPath )
496
527
if err != nil {
497
528
return nil , err
@@ -504,52 +535,51 @@ func resolveBackendVersion() (*version.Version, error) {
504
535
505
536
var info ProductInfo
506
537
err = json .Unmarshal (content , & info )
538
+ return & info , err
539
+ }
540
+
541
+ func syncOptions (launchCtx * LaunchContext ) error {
542
+ srcDir := fmt .Sprintf ("%s/.gitpod/%s/options" , launchCtx .projectDir , launchCtx .alias )
543
+ srcStat , err := os .Stat (srcDir )
544
+ if os .IsNotExist (err ) {
545
+ // nothing to sync
546
+ return nil
547
+ }
507
548
if err != nil {
508
- return nil , err
549
+ return err
550
+ }
551
+ if ! srcStat .IsDir () {
552
+ return fmt .Errorf ("%s is not a directory" , srcDir )
509
553
}
510
- return version .NewVersion (info .Version )
554
+ destDir := fmt .Sprintf ("%s/options" , launchCtx .projectConfigDir )
555
+ _ , err = os .Stat (destDir )
556
+ if os .IsNotExist (err ) {
557
+ return cp .Copy (srcDir , destDir )
558
+ }
559
+ // already synced skipping, i.e. restart of jb backend
560
+ return nil
511
561
}
512
562
513
- func installPlugins (repoRoot string , config * gitpod.GitpodConfig , alias string ) error {
514
- plugins , err := getPlugins (config , alias )
563
+ func installPlugins (config * gitpod.GitpodConfig , launchCtx * LaunchContext ) error {
564
+ plugins , err := getPlugins (config , launchCtx . alias )
515
565
if err != nil {
516
566
return err
517
567
}
518
568
if len (plugins ) <= 0 {
519
569
return nil
520
570
}
521
- r , w , err := os .Pipe ()
522
- if err != nil {
523
- return err
524
- }
525
- defer r .Close ()
526
-
527
- outC := make (chan string )
528
- go func () {
529
- var buf bytes.Buffer
530
- _ , _ = io .Copy (& buf , r )
531
- outC <- buf .String ()
532
- }()
533
571
534
572
var args []string
535
573
args = append (args , "installPlugins" )
536
- args = append (args , repoRoot )
574
+ args = append (args , launchCtx . projectDir )
537
575
args = append (args , plugins ... )
538
- cmd := remoteDevServerCmd (args )
539
- cmd .Stdout = io .MultiWriter (w , os .Stdout )
576
+ cmd := remoteDevServerCmd (args , launchCtx )
540
577
installErr := cmd .Run ()
541
578
542
579
// delete alien_plugins.txt to suppress 3rd-party plugins consent on startup to workaround backend startup freeze
543
- w .Close ()
544
- out := <- outC
545
- configR := regexp .MustCompile ("IDE config directory: (\\ S+)\n " )
546
- matches := configR .FindStringSubmatch (out )
547
- if len (matches ) == 2 {
548
- configDir := matches [1 ]
549
- err := os .Remove (configDir + "/alien_plugins.txt" )
550
- if err != nil {
551
- log .WithError (err ).Error ("failed to suppress 3rd-party plugins consent" )
552
- }
580
+ err = os .Remove (launchCtx .projectConfigDir + "/alien_plugins.txt" )
581
+ if err != nil {
582
+ log .WithError (err ).Error ("failed to suppress 3rd-party plugins consent" )
553
583
}
554
584
555
585
if installErr != nil {
0 commit comments