Skip to content

Commit ade6376

Browse files
committed
fix #3162: decouple gp env from theia
1 parent 9e6635d commit ade6376

File tree

20 files changed

+1349
-333
lines changed

20 files changed

+1349
-333
lines changed

components/gitpod-cli/BUILD.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,9 @@ packages:
88
env:
99
- CGO_ENABLED=0
1010
- GOOS=linux
11+
deps:
12+
- components/common-go:lib
13+
- components/supervisor-api/go:lib
14+
- components/gitpod-protocol/go:lib
1115
config:
1216
packaging: app

components/gitpod-cli/cmd/env.go

Lines changed: 228 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@
55
package cmd
66

77
import (
8+
"context"
9+
"errors"
810
"fmt"
911
"os"
1012
"strings"
13+
"sync"
14+
"time"
1115

1216
"github.com/spf13/cobra"
17+
"golang.org/x/xerrors"
18+
"google.golang.org/grpc"
1319

1420
"github.com/gitpod-io/gitpod/gitpod-cli/pkg/theialib"
21+
serverapi "github.com/gitpod-io/gitpod/gitpod-protocol"
22+
supervisor "github.com/gitpod-io/gitpod/supervisor/api"
1523
)
1624

1725
var exportEnvs = false
@@ -42,82 +50,243 @@ delete environment variables with a repository pattern of */foo, foo/* or */*.
4250
`,
4351
Args: cobra.ArbitraryArgs,
4452
Run: func(cmd *cobra.Command, args []string) {
45-
fail := func(msg string) {
46-
fmt.Fprintln(os.Stderr, msg)
47-
os.Exit(-1)
53+
if len(args) > 0 {
54+
if unsetEnvs {
55+
deleteEnvs(args)
56+
return
57+
}
58+
59+
setEnvs(args)
60+
} else {
61+
getEnvs()
4862
}
63+
},
64+
}
4965

50-
service, err := theialib.NewServiceFromEnv()
66+
type connectToServerResult struct {
67+
repositoryPattern string
68+
client *serverapi.APIoverJSONRPC
69+
}
70+
71+
func connectToServer(ctx context.Context) (*connectToServerResult, error) {
72+
supervisorAddr := os.Getenv("SUPERVISOR_ADDR")
73+
if supervisorAddr == "" {
74+
supervisorAddr = "localhost:22999"
75+
}
76+
supervisorConn, err := grpc.Dial(supervisorAddr, grpc.WithInsecure())
77+
if err != nil {
78+
return nil, xerrors.Errorf("failed connecting to supervisor: %w", err)
79+
}
80+
wsinfo, err := supervisor.NewInfoServiceClient(supervisorConn).WorkspaceInfo(ctx, &supervisor.WorkspaceInfoRequest{})
81+
if err != nil {
82+
return nil, xerrors.Errorf("failed getting workspace info from supervisor: %w", err)
83+
}
84+
if wsinfo.Repository == nil {
85+
return nil, xerrors.New("workspace info is missing repository")
86+
}
87+
if wsinfo.Repository.Owner == "" {
88+
return nil, xerrors.New("repository info is missing owner")
89+
}
90+
if wsinfo.Repository.Name == "" {
91+
return nil, xerrors.New("repository info is missing name")
92+
}
93+
repositoryPattern := wsinfo.Repository.Owner + "/" + wsinfo.Repository.Name
94+
clientToken, err := supervisor.NewTokenServiceClient(supervisorConn).GetToken(ctx, &supervisor.GetTokenRequest{
95+
Host: wsinfo.GitpodApi.Host,
96+
Kind: "gitpod",
97+
Scope: []string{
98+
"function:getEnvVars",
99+
"function:setEnvVar",
100+
"function:deleteEnvVar",
101+
"resource:envVar::" + repositoryPattern + "::create/get/update/delete",
102+
},
103+
})
104+
if err != nil {
105+
return nil, xerrors.Errorf("failed getting token from supervisor: %w", err)
106+
}
107+
client, err := serverapi.ConnectToServer(wsinfo.GitpodApi.Endpoint, serverapi.ConnectToServerOpts{Token: clientToken.Token, Context: ctx})
108+
if err != nil {
109+
return nil, xerrors.Errorf("failed connecting to server: %w", err)
110+
}
111+
return &connectToServerResult{repositoryPattern, client}, nil
112+
}
113+
114+
func getEnvs() {
115+
if !isTheiaIDE() {
116+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
117+
defer cancel()
118+
result, err := connectToServer(ctx)
51119
if err != nil {
52120
fail(err.Error())
53121
}
54122

55-
setEnvs := func() {
56-
vars := make([]theialib.EnvironmentVariable, len(args))
57-
for i, arg := range args {
58-
kv := strings.Split(arg, "=")
59-
if len(kv) != 2 {
60-
fail(fmt.Sprintf("%s has no value (correct format is %s=some_value)", arg, arg))
61-
}
123+
vars, err := result.client.GetEnvVars(ctx)
124+
if err != nil {
125+
fail("failed to fetch env vars from server: " + err.Error())
126+
}
62127

63-
key := strings.TrimSpace(kv[0])
64-
if key == "" {
65-
fail(fmt.Sprintf("variable must have a name"))
66-
}
67-
// Do not trim value - the user might want whitespace here
68-
// Also do not check if the value is empty, as an empty value means we want to delete the variable
69-
val := kv[1]
70-
if val == "" {
71-
fail(fmt.Sprintf("variable must have a value; use -u to unset a variable"))
72-
}
128+
for _, v := range vars {
129+
printVar(v, exportEnvs)
130+
}
131+
return
132+
}
73133

74-
vars[i] = theialib.EnvironmentVariable{Name: key, Value: val}
75-
}
134+
service, err := theialib.NewServiceFromEnv()
135+
if err != nil {
136+
fail(err.Error())
137+
}
76138

77-
_, err = service.SetEnvVar(theialib.SetEnvvarRequest{Variables: vars})
78-
if err != nil {
79-
fail(fmt.Sprintf("cannot set environment variables: %v", err))
80-
}
139+
vars, err := service.GetEnvVars(theialib.GetEnvvarsRequest{})
140+
if err != nil {
141+
fail(fmt.Sprintf("cannot get environment variables: %v", err))
142+
}
81143

82-
for _, v := range vars {
83-
printVar(v, exportEnvs)
84-
}
144+
for _, v := range vars.Variables {
145+
printVarFromTheia(v, exportEnvs)
146+
}
147+
}
148+
149+
func setEnvs(args []string) {
150+
if !isTheiaIDE() {
151+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
152+
defer cancel()
153+
result, err := connectToServer(ctx)
154+
if err != nil {
155+
fail(err.Error())
85156
}
86-
getEnvs := func() {
87-
vars, err := service.GetEnvVars(theialib.GetEnvvarsRequest{})
88-
if err != nil {
89-
fail(fmt.Sprintf("cannot get environment variables: %v", err))
157+
158+
vars := make([]*serverapi.UserEnvVarValue, len(args))
159+
for i, arg := range args {
160+
kv := strings.Split(arg, "=")
161+
if len(kv) != 2 {
162+
fail(fmt.Sprintf("%s has no value (correct format is %s=some_value)", arg, arg))
90163
}
91164

92-
for _, v := range vars.Variables {
93-
printVar(v, exportEnvs)
165+
key := strings.TrimSpace(kv[0])
166+
if key == "" {
167+
fail(fmt.Sprintf("variable must have a name"))
94168
}
95-
}
96-
doUnsetEnvs := func() {
97-
resp, err := service.DeleteEnvVar(theialib.DeleteEnvvarRequest{Variables: args})
98-
if err != nil {
99-
fail(fmt.Sprintf("cannot unset environment variables: %v", err))
169+
// Do not trim value - the user might want whitespace here
170+
// Also do not check if the value is empty, as an empty value means we want to delete the variable
171+
val := kv[1]
172+
if val == "" {
173+
fail(fmt.Sprintf("variable must have a value; use -u to unset a variable"))
100174
}
101175

102-
if len(resp.NotDeleted) != 0 {
103-
fail(fmt.Sprintf("cannot unset environment variables: %s", strings.Join(resp.NotDeleted, ", ")))
104-
}
176+
vars[i] = &serverapi.UserEnvVarValue{Name: key, Value: val, RepositoryPattern: result.repositoryPattern}
105177
}
106178

107-
if len(args) > 0 {
108-
if unsetEnvs {
109-
doUnsetEnvs()
110-
return
111-
}
179+
var exitCode int
180+
var wg sync.WaitGroup
181+
wg.Add(len(vars))
182+
for _, v := range vars {
183+
go func(v *serverapi.UserEnvVarValue) {
184+
err = result.client.SetEnvVar(ctx, v)
185+
if err != nil {
186+
fmt.Fprintln(os.Stderr, fmt.Sprintf("cannot set %s: %v", v.Name, err))
187+
exitCode = -1
188+
} else {
189+
printVar(v, exportEnvs)
190+
}
191+
wg.Done()
192+
}(v)
193+
}
194+
wg.Wait()
195+
os.Exit(exitCode)
196+
}
112197

113-
setEnvs()
114-
} else {
115-
getEnvs()
198+
service, err := theialib.NewServiceFromEnv()
199+
if err != nil {
200+
fail(err.Error())
201+
}
202+
203+
vars := make([]theialib.EnvironmentVariable, len(args))
204+
for i, arg := range args {
205+
kv := strings.Split(arg, "=")
206+
if len(kv) != 2 {
207+
fail(fmt.Sprintf("%s has no value (correct format is %s=some_value)", arg, arg))
116208
}
117-
},
209+
210+
key := strings.TrimSpace(kv[0])
211+
if key == "" {
212+
fail(fmt.Sprintf("variable must have a name"))
213+
}
214+
// Do not trim value - the user might want whitespace here
215+
// Also do not check if the value is empty, as an empty value means we want to delete the variable
216+
val := kv[1]
217+
if val == "" {
218+
fail(fmt.Sprintf("variable must have a value; use -u to unset a variable"))
219+
}
220+
221+
vars[i] = theialib.EnvironmentVariable{Name: key, Value: val}
222+
}
223+
224+
_, err = service.SetEnvVar(theialib.SetEnvvarRequest{Variables: vars})
225+
if err != nil {
226+
fail(fmt.Sprintf("cannot set environment variables: %v", err))
227+
}
228+
229+
for _, v := range vars {
230+
printVarFromTheia(v, exportEnvs)
231+
}
232+
}
233+
234+
func deleteEnvs(args []string) {
235+
if !isTheiaIDE() {
236+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
237+
defer cancel()
238+
result, err := connectToServer(ctx)
239+
if err != nil {
240+
fail(err.Error())
241+
}
242+
243+
var exitCode int
244+
var wg sync.WaitGroup
245+
wg.Add(len(args))
246+
for _, name := range args {
247+
go func(name string) {
248+
err = result.client.DeleteEnvVar(ctx, &serverapi.UserEnvVarValue{Name: name, RepositoryPattern: result.repositoryPattern})
249+
if err != nil {
250+
fmt.Fprintln(os.Stderr, fmt.Sprintf("cannot unset %s: %v", name, err))
251+
exitCode = -1
252+
}
253+
wg.Done()
254+
}(name)
255+
}
256+
wg.Wait()
257+
os.Exit(exitCode)
258+
}
259+
260+
service, err := theialib.NewServiceFromEnv()
261+
if err != nil {
262+
fail(err.Error())
263+
}
264+
265+
resp, err := service.DeleteEnvVar(theialib.DeleteEnvvarRequest{Variables: args})
266+
if err != nil {
267+
fail(fmt.Sprintf("cannot unset environment variables: %v", err))
268+
}
269+
270+
if len(resp.NotDeleted) != 0 {
271+
fail(fmt.Sprintf("cannot unset environment variables: %s", strings.Join(resp.NotDeleted, ", ")))
272+
}
273+
}
274+
275+
func fail(msg string) {
276+
fmt.Fprintln(os.Stderr, msg)
277+
os.Exit(-1)
118278
}
119279

120-
func printVar(v theialib.EnvironmentVariable, export bool) {
280+
func printVar(v *serverapi.UserEnvVarValue, export bool) {
281+
val := strings.Replace(v.Value, "\"", "\\\"", -1)
282+
if export {
283+
fmt.Printf("export %s=\"%s\"\n", v.Name, val)
284+
} else {
285+
fmt.Printf("%s=%s\n", v.Name, val)
286+
}
287+
}
288+
289+
func printVarFromTheia(v theialib.EnvironmentVariable, export bool) {
121290
val := strings.Replace(v.Value, "\"", "\\\"", -1)
122291
if export {
123292
fmt.Printf("export %s=\"%s\"\n", v.Name, val)
@@ -132,3 +301,8 @@ func init() {
132301
envCmd.Flags().BoolVarP(&exportEnvs, "export", "e", false, "produce a script that can be eval'ed in Bash")
133302
envCmd.Flags().BoolVarP(&unsetEnvs, "unset", "u", false, "deletes/unsets persisted environment variables")
134303
}
304+
305+
func isTheiaIDE() bool {
306+
stat, err := os.Stat("/theia")
307+
return !errors.Is(os.ErrNotExist, err) && stat != nil && stat.IsDir()
308+
}

0 commit comments

Comments
 (0)