Skip to content

Commit 476fab2

Browse files
committed
[server] Use workspace cluster as image-builder
1 parent 484a0b8 commit 476fab2

File tree

7 files changed

+100
-19
lines changed

7 files changed

+100
-19
lines changed

components/image-builder-api/typescript/src/sugar.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import { BuildRequest, BuildResponse, BuildStatus, LogsRequest, LogsResponse, Re
1515
import { injectable, inject, optional } from 'inversify';
1616
import * as grpc from "@grpc/grpc-js";
1717
import { TextDecoder } from "util";
18-
import { ImageBuildLogInfo } from "@gitpod/gitpod-protocol";
18+
import { ImageBuildLogInfo, User, Workspace, WorkspaceInstance } from "@gitpod/gitpod-protocol";
1919

2020
export const ImageBuilderClientProvider = Symbol("ImageBuilderClientProvider");
2121

2222
// ImageBuilderClientProvider caches image builder connections
2323
export interface ImageBuilderClientProvider {
24-
getDefault(): PromisifiedImageBuilderClient
24+
getDefault(user: User, workspace: Workspace, instance: WorkspaceInstance): Promise<PromisifiedImageBuilderClient>
2525
}
2626

2727
function withTracing(ctx: TraceContext) {
@@ -53,7 +53,7 @@ export class CachingImageBuilderClientProvider implements ImageBuilderClientProv
5353
// Thus it makes sense to cache them rather than create a new connection for each request.
5454
protected connectionCache: PromisifiedImageBuilderClient | undefined;
5555

56-
getDefault() {
56+
async getDefault(user: User, workspace: Workspace, instance: WorkspaceInstance) {
5757
let interceptors: grpc.Interceptor[] = [];
5858
if (this.clientCallMetrics) {
5959
interceptors = [ createClientCallMetricsInterceptor(this.clientCallMetrics) ];
@@ -78,6 +78,18 @@ export class CachingImageBuilderClientProvider implements ImageBuilderClientProv
7878
return connection;
7979
}
8080

81+
promisify(c: ImageBuilderClient): PromisifiedImageBuilderClient {
82+
let interceptors: grpc.Interceptor[] = [];
83+
if (this.clientCallMetrics) {
84+
interceptors = [ createClientCallMetricsInterceptor(this.clientCallMetrics) ];
85+
}
86+
87+
return new PromisifiedImageBuilderClient(
88+
new ImageBuilderClient(this.clientConfig.address, grpc.credentials.createInsecure()),
89+
interceptors
90+
);
91+
}
92+
8193
}
8294

8395
// StagedBuildResponse captures the multi-stage nature (starting, running, done) of image builds.

components/server/src/container-module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ import { ReferrerPrefixParser } from "./workspace/referrer-prefix-context-parser
9999
import { InstallationAdminTelemetryDataProvider } from "./installation-admin/telemetry-data-provider";
100100
import { IDEService } from "./ide-service";
101101
import { LicenseEvaluator } from "@gitpod/licensor/lib";
102+
import { WorkspaceClusterImagebuilderClientProvider } from "./workspace/workspace-cluster-imagebuilder-client-provider";
102103

103104
export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
104105
bind(Config).toConstantValue(ConfigFile.fromFile());
@@ -162,7 +163,8 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
162163
return { address: config.imageBuilderAddr };
163164
});
164165
bind(CachingImageBuilderClientProvider).toSelf().inSingletonScope();
165-
bind(ImageBuilderClientProvider).toService(CachingImageBuilderClientProvider);
166+
bind(WorkspaceClusterImagebuilderClientProvider).toSelf().inSingletonScope();
167+
bind(ImageBuilderClientProvider).toService(WorkspaceClusterImagebuilderClientProvider);
166168
bind(ImageBuilderClientCallMetrics).toService(IClientCallMetrics);
167169

168170
/* The binding order of the context parser does not configure preference/a working order. Each context parser must be able

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1562,7 +1562,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
15621562

15631563
log.warn(logCtx, "imageBuild logs: fallback!");
15641564
ctx.span?.setTag("workspace.imageBuild.logs.fallback", true);
1565-
await this.deprecatedDoWatchWorkspaceImageBuildLogs(ctx, logCtx, workspace);
1565+
await this.deprecatedDoWatchWorkspaceImageBuildLogs(ctx, logCtx, user, workspace);
15661566
return;
15671567
}
15681568

@@ -1611,6 +1611,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
16111611
protected async deprecatedDoWatchWorkspaceImageBuildLogs(
16121612
ctx: TraceContext,
16131613
logCtx: LogContext,
1614+
user: User,
16141615
workspace: Workspace,
16151616
) {
16161617
if (!workspace.imageNameResolved) {
@@ -1619,7 +1620,11 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
16191620
}
16201621

16211622
try {
1622-
const imgbuilder = this.imageBuilderClientProvider.getDefault();
1623+
const imgbuilder = await this.imageBuilderClientProvider.getDefault(
1624+
user,
1625+
workspace,
1626+
{} as WorkspaceInstance,
1627+
);
16231628
const req = new LogsRequest();
16241629
req.setCensored(true);
16251630
req.setBuildRef(workspace.imageNameResolved);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { Workspace, WorkspaceInstance } from "@gitpod/gitpod-protocol";
8+
import { IClientCallMetrics } from "@gitpod/gitpod-protocol/lib/messaging/client-call-metrics";
9+
import { defaultGRPCOptions } from "@gitpod/gitpod-protocol/lib/util/grpc";
10+
import {
11+
ImageBuilderClient,
12+
ImageBuilderClientCallMetrics,
13+
ImageBuilderClientProvider,
14+
PromisifiedImageBuilderClient,
15+
} from "@gitpod/image-builder/lib";
16+
import { WorkspaceManagerClientProvider } from "@gitpod/ws-manager/lib/client-provider";
17+
import {
18+
WorkspaceManagerClientProviderCompositeSource,
19+
WorkspaceManagerClientProviderSource,
20+
} from "@gitpod/ws-manager/lib/client-provider-source";
21+
import { ExtendedUser } from "@gitpod/ws-manager/lib/constraints";
22+
import { inject, injectable, optional } from "inversify";
23+
24+
@injectable()
25+
export class WorkspaceClusterImagebuilderClientProvider implements ImageBuilderClientProvider {
26+
@inject(WorkspaceManagerClientProviderCompositeSource)
27+
protected readonly source: WorkspaceManagerClientProviderSource;
28+
@inject(WorkspaceManagerClientProvider) protected readonly clientProvider: WorkspaceManagerClientProvider;
29+
@inject(ImageBuilderClientCallMetrics) @optional() protected readonly clientCallMetrics: IClientCallMetrics;
30+
31+
// gRPC connections can be used concurrently, even across services.
32+
// Thus it makes sense to cache them rather than create a new connection for each request.
33+
protected readonly connectionCache = new Map<string, ImageBuilderClient>();
34+
35+
async getDefault(
36+
user: ExtendedUser,
37+
workspace: Workspace,
38+
instance: WorkspaceInstance,
39+
): Promise<PromisifiedImageBuilderClient> {
40+
const clusters = await this.clientProvider.getStartClusterSets(user, workspace, instance);
41+
for await (let cluster of clusters) {
42+
const info = await this.source.getWorkspaceCluster(cluster.installation);
43+
if (!info) {
44+
continue;
45+
}
46+
47+
var client = this.connectionCache.get(info.name);
48+
if (!client) {
49+
client = this.clientProvider.createConnection(ImageBuilderClient, info, defaultGRPCOptions);
50+
this.connectionCache.set(info.name, client);
51+
}
52+
return new PromisifiedImageBuilderClient(client, []);
53+
}
54+
55+
throw new Error("no image-builder available");
56+
}
57+
}

components/server/src/workspace/workspace-starter.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,11 @@ export class WorkspaceStarter {
249249
auth.setTotal(allowAll);
250250
req.setAuth(auth);
251251

252-
const client = this.imagebuilderClientProvider.getDefault();
252+
const client = await this.imagebuilderClientProvider.getDefault(
253+
user,
254+
workspace,
255+
{} as WorkspaceInstance,
256+
);
253257
const res = await client.resolveBaseImage({ span }, req);
254258
workspace.imageSource = <WorkspaceImageSourceReference>{
255259
baseImageResolved: res.getRef(),
@@ -902,7 +906,7 @@ export class WorkspaceStarter {
902906
): Promise<boolean> {
903907
const span = TraceContext.startSpan("needsImageBuild", ctx);
904908
try {
905-
const client = this.imagebuilderClientProvider.getDefault();
909+
const client = await this.imagebuilderClientProvider.getDefault(user, workspace, instance);
906910
const { src, auth, disposable } = await this.prepareBuildRequest(
907911
{ span },
908912
workspace,
@@ -942,7 +946,7 @@ export class WorkspaceStarter {
942946

943947
try {
944948
// Start build...
945-
const client = this.imagebuilderClientProvider.getDefault();
949+
const client = await this.imagebuilderClientProvider.getDefault(user, workspace, instance);
946950
const { src, auth, disposable } = await this.prepareBuildRequest(
947951
{ span },
948952
workspace,

components/ws-manager-api/typescript/src/client-provider.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,14 @@ export class WorkspaceManagerClientProvider implements Disposable {
9292
let client = this.connectionCache.get(name);
9393
if (!client) {
9494
const info = await getConnectionInfo();
95-
client = this.createClient(info, grpcOptions);
95+
client = client = this.createConnection(WorkspaceManagerClient, info, grpcOptions);
9696
this.connectionCache.set(name, client);
9797
} else if (client.getChannel().getConnectivityState(true) != grpc.connectivityState.READY) {
9898
client.close();
9999

100100
console.warn(`Lost connection to workspace manager \"${name}\" - attempting to reestablish`);
101101
const info = await getConnectionInfo();
102-
client = this.createClient(info, grpcOptions);
102+
client = this.createConnection(WorkspaceManagerClient, info, grpcOptions);
103103
this.connectionCache.set(name, client);
104104
}
105105

@@ -119,7 +119,12 @@ export class WorkspaceManagerClientProvider implements Disposable {
119119
return this.source.getAllWorkspaceClusters();
120120
}
121121

122-
public createClient(info: WorkspaceManagerConnectionInfo, grpcOptions?: object): WorkspaceManagerClient {
122+
public createConnection<T extends grpc.Client>(creator: { new(address: string, credentials: grpc.ChannelCredentials, options?: grpc.ClientOptions): T }, info: WorkspaceManagerConnectionInfo, grpcOptions?: object): T {
123+
const options: Partial<grpc.ClientOptions> = {
124+
...grpcOptions,
125+
'grpc.ssl_target_name_override': "ws-manager", // this makes sure we can call ws-manager with a URL different to "ws-manager"
126+
};
127+
123128
let credentials: grpc.ChannelCredentials;
124129
if (info.tls) {
125130
const rootCerts = Buffer.from(info.tls.ca, "base64");
@@ -131,11 +136,7 @@ export class WorkspaceManagerClientProvider implements Disposable {
131136
credentials = grpc.credentials.createInsecure();
132137
}
133138

134-
const options: Partial<grpc.ClientOptions> = {
135-
...grpcOptions,
136-
'grpc.ssl_target_name_override': "ws-manager", // this makes sure we can call ws-manager with a URL different to "ws-manager"
137-
};
138-
return new WorkspaceManagerClient(info.url, credentials, options);
139+
return new creator(info.url, credentials, options);
139140
}
140141

141142
public dispose() {

components/ws-manager-bridge/src/cluster-service-server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
UpdateResponse,
3535
AdmissionConstraint as GRPCAdmissionConstraint,
3636
} from "@gitpod/ws-manager-bridge-api/lib";
37-
import { GetWorkspacesRequest } from "@gitpod/ws-manager/lib";
37+
import { GetWorkspacesRequest, WorkspaceManagerClient } from "@gitpod/ws-manager/lib";
3838
import { WorkspaceManagerClientProvider } from "@gitpod/ws-manager/lib/client-provider";
3939
import {
4040
WorkspaceManagerClientProviderCompositeSource,
@@ -151,7 +151,7 @@ export class ClusterService implements IClusterServiceServer {
151151

152152
// try to connect to validate the config. Throws an exception if it fails.
153153
await new Promise<void>((resolve, reject) => {
154-
const c = this.clientProvider.createClient(newCluster);
154+
const c = this.clientProvider.createConnection(WorkspaceManagerClient, newCluster);
155155
c.getWorkspaces(new GetWorkspacesRequest(), (err: any) => {
156156
if (err) {
157157
reject(

0 commit comments

Comments
 (0)