-
Notifications
You must be signed in to change notification settings - Fork 318
/
vscode.go
202 lines (177 loc) · 4.68 KB
/
vscode.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package vscode
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/loft-sh/devpod/pkg/command"
"github.com/loft-sh/devpod/pkg/config"
copy2 "github.com/loft-sh/devpod/pkg/copy"
"github.com/loft-sh/devpod/pkg/ide"
"github.com/loft-sh/log"
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
const (
OpenNewWindow = "OPEN_NEW_WINDOW"
)
var Options = ide.Options{
OpenNewWindow: {
Name: OpenNewWindow,
Description: "If true, DevPod will open the project in a new vscode window",
Default: "true",
Enum: []string{
"false",
"true",
},
},
}
func NewVSCodeServer(extensions []string, settings string, userName string, values map[string]config.OptionValue, log log.Logger) *VsCodeServer {
return &VsCodeServer{
values: values,
extensions: extensions,
settings: settings,
userName: userName,
log: log,
}
}
type VsCodeServer struct {
values map[string]config.OptionValue
extensions []string
settings string
userName string
log log.Logger
}
func (o *VsCodeServer) InstallExtensions() error {
location, err := PrepareVSCodeServerLocation(o.userName, false)
if err != nil {
return err
}
// wait until vscode server is installed
binPath := ""
binDir := filepath.Join(location, "bin")
for {
entries, err := os.ReadDir(binDir)
if err != nil {
o.log.Debugf("Read dir %s: %v", binDir, err)
o.log.Info("Wait until vscode-server is installed...")
time.Sleep(time.Second * 3)
continue
} else if len(entries) == 0 {
o.log.Debugf("Read dir %s: install dir is missing", binDir)
o.log.Info("Wait until vscode-server is installed...")
time.Sleep(time.Second * 3)
continue
}
binPath = filepath.Join(binDir, entries[0].Name(), "bin", "code-server")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*4)
out, err := exec.CommandContext(ctx, binPath, "--help").CombinedOutput()
cancel()
if err != nil {
o.log.Infof("Execute %s: %v", binPath, command.WrapCommandError(out, err))
o.log.Info("Wait until vscode-server is installed...")
time.Sleep(time.Second * 3)
continue
}
break
}
// start log writer
writer := o.log.Writer(logrus.InfoLevel, false)
defer writer.Close()
// download extensions
for _, extension := range o.extensions {
o.log.Info("Install extension " + extension + "...")
runCommand := fmt.Sprintf("%s serve-local --accept-server-license-terms --install-extension '%s'", binPath, extension)
args := []string{}
if o.userName != "" {
args = append(args, "su", o.userName, "-c", runCommand)
} else {
args = append(args, "sh", "-c", runCommand)
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = writer
cmd.Stderr = writer
err := cmd.Run()
if err != nil {
o.log.Info("Failed installing extension " + extension)
}
o.log.Info("Successfully installed extension " + extension)
}
return nil
}
func (o *VsCodeServer) Install() error {
location, err := PrepareVSCodeServerLocation(o.userName, true)
if err != nil {
return err
}
settingsDir := filepath.Join(location, "data", "Machine")
err = os.MkdirAll(settingsDir, 0777)
if err != nil {
return err
}
// is installed
settingsFile := filepath.Join(settingsDir, "settings.json")
_, err = os.Stat(settingsFile)
if err == nil {
return nil
}
// install requirements alpine
if command.Exists("apk") {
o.log.Debugf("Install vscode dependencies...")
dependencies := []string{"build-base", "gcompat"}
if !command.Exists("git") {
dependencies = append(dependencies, "git")
}
if !command.Exists("bash") {
dependencies = append(dependencies, "bash")
}
if !command.Exists("curl") {
dependencies = append(dependencies, "curl")
}
out, err := exec.Command("sh", "-c", "apk update && apk add "+strings.Join(dependencies, " ")).CombinedOutput()
if err != nil {
o.log.Infof("Error updating alpine: %w", command.WrapCommandError(out, err))
}
}
// add settings
if o.settings == "" {
o.settings = "{}"
}
// set settings
err = os.WriteFile(settingsFile, []byte(o.settings), 0666)
if err != nil {
return err
}
// chown location
if o.userName != "" {
err = copy2.ChownR(location, o.userName)
if err != nil {
return errors.Wrap(err, "chown")
}
}
return nil
}
func PrepareVSCodeServerLocation(userName string, create bool) (string, error) {
var err error
homeFolder := ""
if userName != "" {
homeFolder, err = command.GetHome(userName)
} else {
homeFolder, err = homedir.Dir()
}
if err != nil {
return "", err
}
folder := filepath.Join(homeFolder, ".vscode-server")
if create {
err = os.MkdirAll(folder, 0777)
if err != nil {
return "", err
}
}
return folder, nil
}