Skip to content

Commit 532513a

Browse files
akosyakovroboquat
authored andcommitted
fix #12208: gp env to respect workspace env vars
1 parent cca07f1 commit 532513a

File tree

13 files changed

+422
-95
lines changed

13 files changed

+422
-95
lines changed

components/gitpod-cli/cmd/env.go

+42-18
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,10 @@ import (
1717
"github.com/spf13/cobra"
1818
"golang.org/x/sync/errgroup"
1919
"golang.org/x/xerrors"
20-
"google.golang.org/grpc"
21-
"google.golang.org/grpc/credentials/insecure"
2220

23-
"github.com/gitpod-io/gitpod/common-go/util"
21+
"github.com/gitpod-io/gitpod/gitpod-cli/pkg/supervisor"
2422
serverapi "github.com/gitpod-io/gitpod/gitpod-protocol"
25-
supervisor "github.com/gitpod-io/gitpod/supervisor/api"
23+
supervisorapi "github.com/gitpod-io/gitpod/supervisor/api"
2624
)
2725

2826
var exportEnvs = false
@@ -31,8 +29,8 @@ var unsetEnvs = false
3129
// envCmd represents the env command
3230
var envCmd = &cobra.Command{
3331
Use: "env",
34-
Short: "Controls user-defined, persistent environment variables.",
35-
Long: `This command can print and modify the persistent environment variables associated with your user, for this repository.
32+
Short: "Controls workspace environment variables.",
33+
Long: `This command can print and modify the persistent environment variables associated with your workspace.
3634
3735
To set the persistent environment variable 'foo' to the value 'bar' use:
3836
gp env foo=bar
@@ -78,15 +76,20 @@ delete environment variables with a repository pattern of */foo, foo/* or */*.
7876

7977
type connectToServerResult struct {
8078
repositoryPattern string
79+
wsInfo *supervisorapi.WorkspaceInfoResponse
8180
client *serverapi.APIoverJSONRPC
81+
82+
useDeprecatedGetEnvVar bool
8283
}
8384

8485
func connectToServer(ctx context.Context) (*connectToServerResult, error) {
85-
supervisorConn, err := grpc.Dial(util.GetSupervisorAddress(), grpc.WithTransportCredentials(insecure.NewCredentials()))
86+
supervisorClient, err := supervisor.New(ctx)
8687
if err != nil {
8788
return nil, xerrors.Errorf("failed connecting to supervisor: %w", err)
8889
}
89-
wsinfo, err := supervisor.NewInfoServiceClient(supervisorConn).WorkspaceInfo(ctx, &supervisor.WorkspaceInfoRequest{})
90+
defer supervisorClient.Close()
91+
92+
wsinfo, err := supervisorClient.Info.WorkspaceInfo(ctx, &supervisorapi.WorkspaceInfoRequest{})
9093
if err != nil {
9194
return nil, xerrors.Errorf("failed getting workspace info from supervisor: %w", err)
9295
}
@@ -100,16 +103,32 @@ func connectToServer(ctx context.Context) (*connectToServerResult, error) {
100103
return nil, xerrors.New("repository info is missing name")
101104
}
102105
repositoryPattern := wsinfo.Repository.Owner + "/" + wsinfo.Repository.Name
103-
clientToken, err := supervisor.NewTokenServiceClient(supervisorConn).GetToken(ctx, &supervisor.GetTokenRequest{
106+
107+
var useDeprecatedGetEnvVar bool
108+
clientToken, err := supervisorClient.Token.GetToken(ctx, &supervisorapi.GetTokenRequest{
104109
Host: wsinfo.GitpodApi.Host,
105110
Kind: "gitpod",
106111
Scope: []string{
107-
"function:getEnvVars",
112+
"function:getWorkspaceEnvVars",
108113
"function:setEnvVar",
109114
"function:deleteEnvVar",
110115
"resource:envVar::" + repositoryPattern + "::create/get/update/delete",
111116
},
112117
})
118+
if err != nil {
119+
// TODO remove then GetWorkspaceEnvVars is deployed
120+
clientToken, err = supervisorClient.Token.GetToken(ctx, &supervisorapi.GetTokenRequest{
121+
Host: wsinfo.GitpodApi.Host,
122+
Kind: "gitpod",
123+
Scope: []string{
124+
"function:getEnvVars", // TODO remove then getWorkspaceEnvVars is deployed
125+
"function:setEnvVar",
126+
"function:deleteEnvVar",
127+
"resource:envVar::" + repositoryPattern + "::create/get/update/delete",
128+
},
129+
})
130+
useDeprecatedGetEnvVar = true
131+
}
113132
if err != nil {
114133
return nil, xerrors.Errorf("failed getting token from supervisor: %w", err)
115134
}
@@ -121,7 +140,7 @@ func connectToServer(ctx context.Context) (*connectToServerResult, error) {
121140
if err != nil {
122141
return nil, xerrors.Errorf("failed connecting to server: %w", err)
123142
}
124-
return &connectToServerResult{repositoryPattern, client}, nil
143+
return &connectToServerResult{repositoryPattern, wsinfo, client, useDeprecatedGetEnvVar}, nil
125144
}
126145

127146
func getEnvs(ctx context.Context) error {
@@ -131,13 +150,18 @@ func getEnvs(ctx context.Context) error {
131150
}
132151
defer result.client.Close()
133152

134-
vars, err := result.client.GetEnvVars(ctx)
153+
var vars []*serverapi.EnvVar
154+
if !result.useDeprecatedGetEnvVar {
155+
vars, err = result.client.GetWorkspaceEnvVars(ctx, result.wsInfo.WorkspaceId)
156+
} else {
157+
vars, err = result.client.GetEnvVars(ctx)
158+
}
135159
if err != nil {
136160
return xerrors.Errorf("failed to fetch env vars from server: %w", err)
137161
}
138162

139163
for _, v := range vars {
140-
printVar(v, exportEnvs)
164+
printVar(v.Name, v.Value, exportEnvs)
141165
}
142166

143167
return nil
@@ -163,7 +187,7 @@ func setEnvs(ctx context.Context, args []string) error {
163187
if err != nil {
164188
return err
165189
}
166-
printVar(v, exportEnvs)
190+
printVar(v.Name, v.Value, exportEnvs)
167191
return nil
168192
})
169193
}
@@ -189,12 +213,12 @@ func deleteEnvs(ctx context.Context, args []string) error {
189213
return g.Wait()
190214
}
191215

192-
func printVar(v *serverapi.UserEnvVarValue, export bool) {
193-
val := strings.Replace(v.Value, "\"", "\\\"", -1)
216+
func printVar(name string, value string, export bool) {
217+
val := strings.Replace(value, "\"", "\\\"", -1)
194218
if export {
195-
fmt.Printf("export %s=\"%s\"\n", v.Name, val)
219+
fmt.Printf("export %s=\"%s\"\n", name, val)
196220
} else {
197-
fmt.Printf("%s=%s\n", v.Name, val)
221+
fmt.Printf("%s=%s\n", name, val)
198222
}
199223
}
200224

components/gitpod-cli/pkg/supervisor/client.go

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type SupervisorClient struct {
2626
Info api.InfoServiceClient
2727
Notification api.NotificationServiceClient
2828
Control api.ControlServiceClient
29+
Token api.TokenServiceClient
2930
}
3031

3132
type SupervisorClientOption struct {
@@ -51,6 +52,7 @@ func New(ctx context.Context, options ...*SupervisorClientOption) (*SupervisorCl
5152
Info: api.NewInfoServiceClient(conn),
5253
Notification: api.NewNotificationServiceClient(conn),
5354
Control: api.NewControlServiceClient(conn),
55+
Token: api.NewTokenServiceClient(conn),
5456
}, nil
5557
}
5658

components/gitpod-protocol/go/gitpod-service.go

+31-3
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ type APIInterface interface {
6363
ClosePort(ctx context.Context, workspaceID string, port float32) (err error)
6464
GetUserStorageResource(ctx context.Context, options *GetUserStorageResourceOptions) (res string, err error)
6565
UpdateUserStorageResource(ctx context.Context, options *UpdateUserStorageResourceOptions) (err error)
66-
GetEnvVars(ctx context.Context) (res []*UserEnvVarValue, err error)
66+
GetWorkspaceEnvVars(ctx context.Context, workspaceID string) (res []*EnvVar, err error)
67+
GetEnvVars(ctx context.Context) (res []*EnvVar, err error)
6768
SetEnvVar(ctx context.Context, variable *UserEnvVarValue) (err error)
6869
DeleteEnvVar(ctx context.Context, variable *UserEnvVarValue) (err error)
6970
HasSSHPublicKey(ctx context.Context) (res bool, err error)
@@ -1112,15 +1113,35 @@ func (gp *APIoverJSONRPC) UpdateUserStorageResource(ctx context.Context, options
11121113
return
11131114
}
11141115

1116+
// GetWorkspaceEnvVars calls GetWorkspaceEnvVars on the server
1117+
func (gp *APIoverJSONRPC) GetWorkspaceEnvVars(ctx context.Context, workspaceID string) (res []*EnvVar, err error) {
1118+
if gp == nil {
1119+
err = errNotConnected
1120+
return
1121+
}
1122+
var _params []interface{}
1123+
1124+
_params = append(_params, workspaceID)
1125+
1126+
var result []*EnvVar
1127+
err = gp.C.Call(ctx, "getWorkspaceEnvVars", _params, &result)
1128+
if err != nil {
1129+
return
1130+
}
1131+
res = result
1132+
1133+
return
1134+
}
1135+
11151136
// GetEnvVars calls getEnvVars on the server
1116-
func (gp *APIoverJSONRPC) GetEnvVars(ctx context.Context) (res []*UserEnvVarValue, err error) {
1137+
func (gp *APIoverJSONRPC) GetEnvVars(ctx context.Context) (res []*EnvVar, err error) {
11171138
if gp == nil {
11181139
err = errNotConnected
11191140
return
11201141
}
11211142
var _params []interface{}
11221143

1123-
var result []*UserEnvVarValue
1144+
var result []*EnvVar
11241145
err = gp.C.Call(ctx, "getEnvVars", _params, &result)
11251146
if err != nil {
11261147
return
@@ -1962,6 +1983,13 @@ type WhitelistedRepository struct {
19621983
URL string `json:"url,omitempty"`
19631984
}
19641985

1986+
// EnvVar is the EnvVar message type
1987+
type EnvVar struct {
1988+
ID string `json:"id,omitempty"`
1989+
Name string `json:"name,omitempty"`
1990+
Value string `json:"value,omitempty"`
1991+
}
1992+
19651993
// UserEnvVarValue is the UserEnvVarValue message type
19661994
type UserEnvVarValue struct {
19671995
ID string `json:"id,omitempty"`

components/gitpod-protocol/go/mock.go

+11-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/gitpod-protocol/src/gitpod-service.ts

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
UserSSHPublicKeyValue,
2828
SSHPublicKeyValue,
2929
IDESettings,
30+
EnvVarWithValue,
3031
} from "./protocol";
3132
import {
3233
Team,
@@ -153,6 +154,9 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
153154
getUserStorageResource(options: GitpodServer.GetUserStorageResourceOptions): Promise<string>;
154155
updateUserStorageResource(options: GitpodServer.UpdateUserStorageResourceOptions): Promise<void>;
155156

157+
// Workspace env vars
158+
getWorkspaceEnvVars(workspaceId: string): Promise<EnvVarWithValue[]>;
159+
156160
// User env vars
157161
getEnvVars(): Promise<UserEnvVarValue[]>;
158162
getAllEnvVars(): Promise<UserEnvVarValue[]>;

components/gitpod-protocol/src/protocol.ts

+2
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ export namespace NamedWorkspaceFeatureFlag {
323323
}
324324
}
325325

326+
export type EnvVar = UserEnvVar | ProjectEnvVarWithValue | EnvVarWithValue;
327+
326328
export interface EnvVarWithValue {
327329
name: string;
328330
value: string;

components/server/ee/src/prebuilds/prebuild-manager.ts

+9-10
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
CommitInfo,
1111
PrebuiltWorkspace,
1212
Project,
13-
ProjectEnvVar,
1413
StartPrebuildContext,
1514
StartPrebuildResult,
1615
TaskConfig,
@@ -39,6 +38,7 @@ import { ResponseError } from "vscode-ws-jsonrpc";
3938
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
4039
import { UserService } from "../../../src/user/user-service";
4140
import { EntitlementService, MayStartWorkspaceResult } from "../../../src/billing/entitlement-service";
41+
import { EnvVarService } from "../../../src/workspace/env-var-service";
4242

4343
export class WorkspaceRunningError extends Error {
4444
constructor(msg: string, public instance: WorkspaceInstance) {
@@ -67,6 +67,7 @@ export class PrebuildManager {
6767
@inject(UserService) protected readonly userService: UserService;
6868
@inject(TeamDB) protected readonly teamDB: TeamDB;
6969
@inject(EntitlementService) protected readonly entitlementService: EntitlementService;
70+
@inject(EnvVarService) private readonly envVarService: EnvVarService;
7071

7172
async abortPrebuildsForBranch(ctx: TraceContext, project: Project, user: User, branch: string): Promise<void> {
7273
const span = TraceContext.startSpan("abortPrebuildsForBranch", ctx);
@@ -221,8 +222,6 @@ export class PrebuildManager {
221222
}
222223
}
223224

224-
const projectEnvVarsPromise = project ? this.projectService.getProjectEnvironmentVariables(project.id) : [];
225-
226225
let organizationId = (await this.teamDB.findTeamById(project.id))?.id;
227226
if (!user.additionalData?.isMigratedToTeamOnlyAttribution) {
228227
// If the user is not migrated to team-only attribution, we retrieve the organization from the attribution logic.
@@ -238,6 +237,9 @@ export class PrebuildManager {
238237
prebuildContext,
239238
context.normalizedContextURL!,
240239
);
240+
241+
const envVarsPromise = this.envVarService.resolve(workspace);
242+
241243
const prebuild = await this.workspaceDB.trace({ span }).findPrebuildByWorkspaceID(workspace.id)!;
242244
if (!prebuild) {
243245
throw new Error(`Failed to create a prebuild for: ${context.normalizedContextURL}`);
@@ -281,8 +283,8 @@ export class PrebuildManager {
281283
await this.workspaceDB.trace({ span }).storePrebuiltWorkspace(prebuild);
282284
} else {
283285
span.setTag("starting", true);
284-
const projectEnvVars = await projectEnvVarsPromise;
285-
await this.workspaceStarter.startWorkspace({ span }, workspace, user, project, [], projectEnvVars, {
286+
const envVars = await envVarsPromise;
287+
await this.workspaceStarter.startWorkspace({ span }, workspace, user, project, envVars, {
286288
excludeFeatureFlags: ["full_workspace_backup"],
287289
});
288290
}
@@ -341,11 +343,8 @@ export class PrebuildManager {
341343
if (!prebuild) {
342344
throw new Error("No prebuild found for workspace " + workspaceId);
343345
}
344-
let projectEnvVars: ProjectEnvVar[] = [];
345-
if (workspace.projectId) {
346-
projectEnvVars = await this.projectService.getProjectEnvironmentVariables(workspace.projectId);
347-
}
348-
await this.workspaceStarter.startWorkspace({ span }, workspace, user, project, [], projectEnvVars);
346+
const envVars = await this.envVarService.resolve(workspace);
347+
await this.workspaceStarter.startWorkspace({ span }, workspace, user, project, envVars);
349348
return { prebuildId: prebuild.id, wsid: workspace.id, done: false };
350349
} catch (err) {
351350
TraceContext.setError({ span }, err);

components/server/src/auth/rate-limiter.ts

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ const defaultFunctions: FunctionsConfig = {
9090
closePort: { group: "default", points: 1 },
9191
getUserStorageResource: { group: "default", points: 1 },
9292
updateUserStorageResource: { group: "default", points: 1 },
93+
getWorkspaceEnvVars: { group: "default", points: 1 },
9394
getEnvVars: { group: "default", points: 1 },
9495
getAllEnvVars: { group: "default", points: 1 },
9596
setEnvVar: { group: "default", points: 1 },

components/server/src/container-module.ts

+3
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ import { retryMiddleware } from "nice-grpc-client-middleware-retry";
114114
import { IamSessionApp } from "./iam/iam-session-app";
115115
import { spicedbClientFromEnv, SpiceDBClient } from "./authorization/spicedb";
116116
import { Authorizer, PermissionChecker } from "./authorization/perms";
117+
import { EnvVarService } from "./workspace/env-var-service";
117118

118119
export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
119120
bind(Config).toConstantValue(ConfigFile.fromFile());
@@ -256,6 +257,8 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
256257

257258
bind(ProjectsService).toSelf().inSingletonScope();
258259

260+
bind(EnvVarService).toSelf().inSingletonScope();
261+
259262
bind(NewsletterSubscriptionController).toSelf().inSingletonScope();
260263

261264
bind<UsageServiceClient>(UsageServiceDefinition.name)

0 commit comments

Comments
 (0)