Skip to content

Commit 18259dc

Browse files
Simon Emmsroboquat
Simon Emms
authored andcommitted
Create installation admin controller
1 parent 9e5d13a commit 18259dc

11 files changed

+183
-7
lines changed

components/gitpod-db/src/container-module.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ import { ProjectDBImpl } from './typeorm/project-db-impl';
5757
import { EntityManager } from 'typeorm';
5858
import { OssAllowListDB } from './oss-allowlist-db';
5959
import { OssAllowListDBImpl } from './typeorm/oss-allowlist-db-impl';
60+
import { TypeORMInstallationAdminImpl } from './typeorm/installation-admin-db-impl';
61+
import { InstallationAdminDB } from './installation-admin-db';
6062

6163
// THE DB container module that contains all DB implementations
6264
export const dbContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
@@ -72,6 +74,9 @@ export const dbContainerModule = new ContainerModule((bind, unbind, isBound, reb
7274
bind(TermsAcceptanceDB).toService(TermsAcceptanceDBImpl);
7375
bindDbWithTracing(TracedUserDB, bind, UserDB).inSingletonScope();
7476

77+
bind(TypeORMInstallationAdminImpl).toSelf().inSingletonScope();
78+
bind(InstallationAdminDB).toService(TypeORMInstallationAdminImpl);
79+
7580
bind(AuthProviderEntryDB).to(AuthProviderEntryDBImpl).inSingletonScope();
7681

7782
bind(TypeORMWorkspaceDBImpl).toSelf().inSingletonScope();

components/gitpod-db/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ export * from "./edu-email-domain-db";
3636
export * from "./email-domain-filter-db";
3737
export * from "./typeorm/entity/db-account-entry";
3838
export * from "./project-db";
39-
export * from "./team-db";
39+
export * from "./team-db";
40+
export * from "./installation-admin-db";
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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 { InstallationAdmin } from "@gitpod/gitpod-protocol";
8+
9+
export const InstallationAdminDB = Symbol('InstallationAdminDB');
10+
export interface InstallationAdminDB {
11+
getTelemetryData(): Promise<InstallationAdmin>;
12+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright (c) 2021 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 { InstallationAdmin, InstallationAdminSettings } from "@gitpod/gitpod-protocol";
8+
import { Entity, Column, PrimaryColumn } from "typeorm";
9+
import { TypeORM } from "../typeorm";
10+
11+
@Entity()
12+
export class DBInstallationAdmin implements InstallationAdmin {
13+
@PrimaryColumn(TypeORM.UUID_COLUMN_TYPE)
14+
id: string;
15+
16+
@Column('simple-json')
17+
settings: InstallationAdminSettings;
18+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 { inject, injectable, } from 'inversify';
8+
import { InstallationAdmin } from '@gitpod/gitpod-protocol';
9+
import { v4 as uuidv4 } from 'uuid';
10+
import { Repository } from 'typeorm';
11+
import { TypeORM } from './typeorm';
12+
import { InstallationAdminDB } from '../installation-admin-db';
13+
import { DBInstallationAdmin } from './entity/db-installation-admin';
14+
15+
@injectable()
16+
export class TypeORMInstallationAdminImpl implements InstallationAdminDB {
17+
@inject(TypeORM) typeORM: TypeORM;
18+
19+
protected async getEntityManager() {
20+
return (await this.typeORM.getConnection()).manager;
21+
}
22+
23+
protected async createDefaultRecord(): Promise<InstallationAdmin> {
24+
const record: InstallationAdmin = {
25+
id: uuidv4(),
26+
settings: {
27+
sendTelemetry: false,
28+
},
29+
};
30+
31+
const repo = await this.getInstallationAdminRepo();
32+
return repo.save(record);
33+
}
34+
35+
async getInstallationAdminRepo(): Promise<Repository<DBInstallationAdmin>> {
36+
return (await this.getEntityManager()).getRepository<DBInstallationAdmin>(DBInstallationAdmin);
37+
}
38+
39+
/**
40+
* Get Telemetry Data
41+
*
42+
* Returns the first record found or creates a
43+
* new record.
44+
*
45+
* @returns Promise<InstallationAdmin>
46+
*/
47+
async getTelemetryData(): Promise<InstallationAdmin> {
48+
const repo = await this.getInstallationAdminRepo();
49+
const [record] = await repo.find();
50+
51+
if (record) {
52+
return record;
53+
}
54+
55+
/* Record not found - create one */
56+
return this.createDefaultRecord();
57+
}
58+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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 { MigrationInterface, QueryRunner } from "typeorm";
8+
import { tableExists } from "./helper/helper";
9+
10+
export class InstallationAdminTable1642422506330 implements MigrationInterface {
11+
12+
public async up(queryRunner: QueryRunner): Promise<void> {
13+
if (!(await tableExists(queryRunner, "d_b_installation_admin"))) {
14+
await queryRunner.query("CREATE TABLE IF NOT EXISTS `d_b_installation_admin` (`id` char(128) NOT NULL, `settings` text NULL, `_lastModified` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), PRIMARY KEY(`id`)) ENGINE=InnoDB");
15+
}
16+
}
17+
18+
public async down(queryRunner: QueryRunner): Promise<void> {
19+
if (await tableExists(queryRunner, "d_b_installation_admin")) {
20+
await queryRunner.query("DROP TABLE `d_b_installation_admin`");
21+
}
22+
}
23+
24+
}

components/gitpod-protocol/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ export * from './context-url';
1919
export * from './teams-projects-protocol';
2020
export * from './snapshot-url';
2121
export * from './oss-allowlist';
22+
export * from './installation-admin-protocol';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
export interface InstallationAdminSettings {
8+
sendTelemetry: boolean;
9+
}
10+
11+
export interface InstallationAdmin {
12+
id: string;
13+
settings: InstallationAdminSettings;
14+
}

components/server/src/container-module.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { SessionHandlerProvider } from './session-handler';
1212
import { GitpodFileParser } from '@gitpod/gitpod-protocol/lib/gitpod-file-parser';
1313
import { WorkspaceFactory } from './workspace/workspace-factory';
1414
import { UserController } from './user/user-controller';
15+
import { InstallationAdminController } from './installation-admin/installation-admin-controller';
1516
import { GitpodServerImpl } from './workspace/gitpod-server-impl';
1617
import { ConfigProvider } from './workspace/config-provider';
1718
import { MessageBusIntegration } from './workspace/messagebus-integration';
@@ -115,6 +116,8 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
115116
bind(EnforcementControllerServerFactory).toAutoFactory(GitpodServerImpl);
116117
bind(EnforcementController).toSelf().inSingletonScope();
117118

119+
bind(InstallationAdminController).toSelf().inSingletonScope();
120+
118121
bind(MessagebusConfiguration).toSelf().inSingletonScope();
119122
bind(MessageBusHelper).to(MessageBusHelperImpl).inSingletonScope();
120123
bind(MessageBusIntegration).toSelf().inSingletonScope();
@@ -124,11 +127,11 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
124127

125128
bind(GitpodServerImpl).toSelf();
126129
bind(WebsocketConnectionManager).toDynamicValue(ctx => {
127-
const serverFactory = () => ctx.container.get<GitpodServerImpl>(GitpodServerImpl);
128-
const hostContextProvider = ctx.container.get<HostContextProvider>(HostContextProvider);
129-
const config = ctx.container.get<Config>(Config);
130-
return new WebsocketConnectionManager(serverFactory, hostContextProvider, config.rateLimiter);
131-
}
130+
const serverFactory = () => ctx.container.get<GitpodServerImpl>(GitpodServerImpl);
131+
const hostContextProvider = ctx.container.get<HostContextProvider>(HostContextProvider);
132+
const config = ctx.container.get<Config>(Config);
133+
return new WebsocketConnectionManager(serverFactory, hostContextProvider, config.rateLimiter);
134+
}
132135
).inSingletonScope();
133136

134137
bind(PrometheusClientCallMetrics).toSelf().inSingletonScope();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 { injectable, inject } from 'inversify';
8+
import * as express from 'express';
9+
import { InstallationAdminDB } from '@gitpod/gitpod-db/lib';
10+
11+
@injectable()
12+
export class InstallationAdminController {
13+
@inject(InstallationAdminDB) protected readonly installationAdminDb: InstallationAdminDB;
14+
15+
public create(): express.Application {
16+
const app = express();
17+
18+
app.get('/data', async (req: express.Request, res: express.Response) => {
19+
const data = await this.installationAdminDb.getTelemetryData();
20+
21+
res.send(data);
22+
});
23+
24+
return app;
25+
}
26+
}

components/server/src/server.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { Config } from './config';
4444
import { DebugApp } from './debug-app';
4545
import { LocalMessageBroker } from './messaging/local-message-broker';
4646
import { WsConnectionHandler } from './express/ws-connection-handler';
47+
import { InstallationAdminController } from './installation-admin/installation-admin-controller';
4748

4849
@injectable()
4950
export class Server<C extends GitpodClient, S extends GitpodServer> {
@@ -54,6 +55,7 @@ export class Server<C extends GitpodClient, S extends GitpodServer> {
5455
@inject(SessionHandlerProvider) protected sessionHandlerProvider: SessionHandlerProvider;
5556
@inject(Authenticator) protected authenticator: Authenticator;
5657
@inject(UserController) protected readonly userController: UserController;
58+
@inject(InstallationAdminController) protected readonly installationAdminController: InstallationAdminController;
5759
@inject(EnforcementController) protected readonly enforcementController: EnforcementController;
5860
@inject(WebsocketConnectionManager) protected websocketConnectionHandler: WebsocketConnectionManager;
5961
@inject(MessageBusIntegration) protected readonly messagebus: MessageBusIntegration;
@@ -82,7 +84,9 @@ export class Server<C extends GitpodClient, S extends GitpodServer> {
8284
protected app?: express.Application;
8385
protected httpServer?: http.Server;
8486
protected monitoringApp?: express.Application;
87+
protected installationAdminApp?: express.Application;
8588
protected monitoringHttpServer?: http.Server;
89+
protected installationAdminHttpServer?: http.Server;
8690
protected disposables = new DisposableCollection();
8791

8892
public async init(app: express.Application) {
@@ -102,7 +106,7 @@ export class Server<C extends GitpodClient, S extends GitpodServer> {
102106
// metrics
103107
app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
104108
const startTime = Date.now();
105-
req.on("end", () =>{
109+
req.on("end", () => {
106110
const method = req.method;
107111
const route = req.route?.path || req.baseUrl || "unknown";
108112
observeHttpRequestDuration(method, route, res.statusCode, (Date.now() - startTime) / 1000)
@@ -229,6 +233,9 @@ export class Server<C extends GitpodClient, S extends GitpodServer> {
229233
// Health check + metrics endpoints
230234
this.monitoringApp = this.monitoringEndpointsApp.create();
231235

236+
// Installation Admin - host separately to avoid exposing publicly
237+
this.installationAdminApp = this.installationAdminController.create();
238+
232239
// Report current websocket connections
233240
this.installWebsocketConnectionGauge();
234241
this.installWebsocketClientContextGauge();
@@ -302,12 +309,19 @@ export class Server<C extends GitpodClient, S extends GitpodServer> {
302309
});
303310
}
304311

312+
if (this.installationAdminApp) {
313+
this.installationAdminHttpServer = this.installationAdminApp.listen(9000, () => {
314+
log.info(`installation admin app listening on port: ${(<AddressInfo>this.installationAdminHttpServer!.address()).port}`)
315+
})
316+
}
317+
305318
this.debugApp.start(6060);
306319
}
307320

308321
public async stop() {
309322
await this.debugApp.stop();
310323
await this.stopServer(this.monitoringHttpServer);
324+
await this.stopServer(this.installationAdminHttpServer);
311325
await this.stopServer(this.httpServer);
312326
this.disposables.dispose();
313327
log.info('server stopped.');

0 commit comments

Comments
 (0)