Skip to content

Commit a2749b1

Browse files
committed
[server] Use workspace cluster as image-builder
1 parent 2ac8008 commit a2749b1

File tree

7 files changed

+211
-86
lines changed

7 files changed

+211
-86
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
@@ -97,6 +97,7 @@ import { LocalMessageBroker, LocalRabbitMQBackedMessageBroker } from "./messagin
9797
import { contentServiceBinder } from "@gitpod/content-service/lib/sugar";
9898
import { ReferrerPrefixParser } from "./workspace/referrer-prefix-context-parser";
9999
import { InstallationAdminTelemetryDataProvider } from "./installation-admin/telemetry-data-provider";
100+
import { WorkspaceClusterImagebuilderClientProvider } from "./workspace/workspace-cluster-imagebuilder-client-provider";
100101

101102
export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
102103
bind(Config).toConstantValue(ConfigFile.fromFile());
@@ -159,7 +160,8 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
159160
return { address: config.imageBuilderAddr };
160161
});
161162
bind(CachingImageBuilderClientProvider).toSelf().inSingletonScope();
162-
bind(ImageBuilderClientProvider).toService(CachingImageBuilderClientProvider);
163+
bind(WorkspaceClusterImagebuilderClientProvider).toSelf().inSingletonScope();
164+
bind(ImageBuilderClientProvider).toService(WorkspaceClusterImagebuilderClientProvider);
163165
bind(ImageBuilderClientCallMetrics).toService(IClientCallMetrics);
164166

165167
/* 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
@@ -1553,7 +1553,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
15531553

15541554
log.warn(logCtx, "imageBuild logs: fallback!");
15551555
ctx.span?.setTag("workspace.imageBuild.logs.fallback", true);
1556-
await this.deprecatedDoWatchWorkspaceImageBuildLogs(ctx, logCtx, workspace);
1556+
await this.deprecatedDoWatchWorkspaceImageBuildLogs(ctx, logCtx, user, workspace);
15571557
return;
15581558
}
15591559

@@ -1602,6 +1602,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
16021602
protected async deprecatedDoWatchWorkspaceImageBuildLogs(
16031603
ctx: TraceContext,
16041604
logCtx: LogContext,
1605+
user: User,
16051606
workspace: Workspace,
16061607
) {
16071608
if (!workspace.imageNameResolved) {
@@ -1610,7 +1611,11 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
16101611
}
16111612

16121613
try {
1613-
const imgbuilder = this.imageBuilderClientProvider.getDefault();
1614+
const imgbuilder = await this.imageBuilderClientProvider.getDefault(
1615+
user,
1616+
workspace,
1617+
{} as WorkspaceInstance,
1618+
);
16141619
const req = new LogsRequest();
16151620
req.setCensored(true);
16161621
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
@@ -178,7 +178,11 @@ export class WorkspaceStarter {
178178
auth.setTotal(allowAll);
179179
req.setAuth(auth);
180180

181-
const client = this.imagebuilderClientProvider.getDefault();
181+
const client = await this.imagebuilderClientProvider.getDefault(
182+
user,
183+
workspace,
184+
{} as WorkspaceInstance,
185+
);
182186
const res = await client.resolveBaseImage({ span }, req);
183187
workspace.imageSource = <WorkspaceImageSourceReference>{
184188
baseImageResolved: res.getRef(),
@@ -814,7 +818,7 @@ export class WorkspaceStarter {
814818
): Promise<boolean> {
815819
const span = TraceContext.startSpan("needsImageBuild", ctx);
816820
try {
817-
const client = this.imagebuilderClientProvider.getDefault();
821+
const client = await this.imagebuilderClientProvider.getDefault(user, workspace, instance);
818822
const { src, auth, disposable } = await this.prepareBuildRequest(
819823
{ span },
820824
workspace,
@@ -854,7 +858,7 @@ export class WorkspaceStarter {
854858

855859
try {
856860
// Start build...
857-
const client = this.imagebuilderClientProvider.getDefault();
861+
const client = await this.imagebuilderClientProvider.getDefault(user, workspace, instance);
858862
const { src, auth, disposable } = await this.prepareBuildRequest(
859863
{ span },
860864
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() {

0 commit comments

Comments
 (0)