@gitpod/gitpod-protocol 0.1.5-pavel-10259-1.39 → 0.1.5-pavel-fixlog.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/data/gitpod-schema.json +16 -0
  2. package/lib/accounting-protocol.d.ts +1 -0
  3. package/lib/accounting-protocol.d.ts.map +1 -1
  4. package/lib/accounting-protocol.js +4 -0
  5. package/lib/accounting-protocol.js.map +1 -1
  6. package/lib/admin-protocol.d.ts +4 -0
  7. package/lib/admin-protocol.d.ts.map +1 -1
  8. package/lib/admin-protocol.js.map +1 -1
  9. package/lib/attribution.d.ts +20 -0
  10. package/lib/attribution.d.ts.map +1 -0
  11. package/lib/attribution.js +40 -0
  12. package/lib/attribution.js.map +1 -0
  13. package/lib/billing-mode.d.ts +36 -0
  14. package/lib/billing-mode.d.ts.map +1 -0
  15. package/lib/billing-mode.js +45 -0
  16. package/lib/billing-mode.js.map +1 -0
  17. package/lib/blocked-repositories-protocol.d.ts +13 -0
  18. package/lib/blocked-repositories-protocol.d.ts.map +1 -0
  19. package/lib/blocked-repositories-protocol.js +8 -0
  20. package/lib/blocked-repositories-protocol.js.map +1 -0
  21. package/lib/context-url.js +1 -1
  22. package/lib/context-url.js.map +1 -1
  23. package/lib/experiments/always-default.d.ts +8 -0
  24. package/lib/experiments/always-default.d.ts.map +1 -0
  25. package/lib/experiments/always-default.js +20 -0
  26. package/lib/experiments/always-default.js.map +1 -0
  27. package/lib/experiments/configcat-server.d.ts +10 -0
  28. package/lib/experiments/configcat-server.d.ts.map +1 -0
  29. package/lib/experiments/configcat-server.js +36 -0
  30. package/lib/experiments/configcat-server.js.map +1 -0
  31. package/lib/experiments/configcat.d.ts +22 -0
  32. package/lib/experiments/configcat.d.ts.map +1 -0
  33. package/lib/experiments/configcat.js +53 -0
  34. package/lib/experiments/configcat.js.map +1 -0
  35. package/lib/experiments/types.d.ts +23 -0
  36. package/lib/experiments/types.d.ts.map +1 -0
  37. package/lib/experiments/types.js +10 -0
  38. package/lib/experiments/types.js.map +1 -0
  39. package/lib/gitpod-service.d.ts +25 -3
  40. package/lib/gitpod-service.d.ts.map +1 -1
  41. package/lib/gitpod-service.js +1 -1
  42. package/lib/gitpod-service.js.map +1 -1
  43. package/lib/index.d.ts +1 -0
  44. package/lib/index.d.ts.map +1 -1
  45. package/lib/index.js +1 -0
  46. package/lib/index.js.map +1 -1
  47. package/lib/installation-admin-protocol.d.ts +3 -0
  48. package/lib/installation-admin-protocol.d.ts.map +1 -1
  49. package/lib/installation-admin-protocol.js +1 -0
  50. package/lib/installation-admin-protocol.js.map +1 -1
  51. package/lib/messaging/client-call-metrics.d.ts +1 -17
  52. package/lib/messaging/client-call-metrics.d.ts.map +1 -1
  53. package/lib/messaging/client-call-metrics.js +1 -2
  54. package/lib/messaging/client-call-metrics.js.map +1 -1
  55. package/lib/messaging/error.d.ts +3 -1
  56. package/lib/messaging/error.d.ts.map +1 -1
  57. package/lib/messaging/error.js +7 -3
  58. package/lib/messaging/error.js.map +1 -1
  59. package/lib/protocol.d.ts +57 -2
  60. package/lib/protocol.d.ts.map +1 -1
  61. package/lib/protocol.js +91 -3
  62. package/lib/protocol.js.map +1 -1
  63. package/lib/protocol.spec.d.ts +7 -0
  64. package/lib/protocol.spec.d.ts.map +1 -0
  65. package/lib/protocol.spec.js +127 -0
  66. package/lib/protocol.spec.js.map +1 -0
  67. package/lib/team-subscription-protocol.d.ts +1 -0
  68. package/lib/team-subscription-protocol.d.ts.map +1 -1
  69. package/lib/team-subscription-protocol.js +4 -0
  70. package/lib/team-subscription-protocol.js.map +1 -1
  71. package/lib/teams-projects-protocol.d.ts +13 -0
  72. package/lib/teams-projects-protocol.d.ts.map +1 -1
  73. package/lib/teams-projects-protocol.js.map +1 -1
  74. package/lib/usage.d.ts +31 -0
  75. package/lib/usage.d.ts.map +1 -0
  76. package/lib/usage.js +14 -0
  77. package/lib/usage.js.map +1 -0
  78. package/lib/util/debug-app.d.ts +25 -0
  79. package/lib/util/debug-app.d.ts.map +1 -0
  80. package/lib/util/debug-app.js +100 -0
  81. package/lib/util/debug-app.js.map +1 -0
  82. package/lib/util/gitpod-host-url.d.ts +3 -0
  83. package/lib/util/gitpod-host-url.d.ts.map +1 -1
  84. package/lib/util/gitpod-host-url.js +18 -0
  85. package/lib/util/gitpod-host-url.js.map +1 -1
  86. package/lib/util/grpc.d.ts +19 -0
  87. package/lib/util/grpc.d.ts.map +1 -1
  88. package/lib/util/grpc.js +73 -1
  89. package/lib/util/grpc.js.map +1 -1
  90. package/lib/util/logging.d.ts.map +1 -1
  91. package/lib/util/logging.js +8 -1
  92. package/lib/util/logging.js.map +1 -1
  93. package/lib/util/timeutil.d.ts +1 -0
  94. package/lib/util/timeutil.d.ts.map +1 -1
  95. package/lib/util/timeutil.js +7 -1
  96. package/lib/util/timeutil.js.map +1 -1
  97. package/lib/webhook-event.d.ts +44 -0
  98. package/lib/webhook-event.d.ts.map +1 -0
  99. package/lib/webhook-event.js +8 -0
  100. package/lib/webhook-event.js.map +1 -0
  101. package/lib/workspace-class.d.ts +14 -0
  102. package/lib/workspace-class.d.ts.map +1 -0
  103. package/lib/workspace-class.js +8 -0
  104. package/lib/workspace-class.js.map +1 -0
  105. package/lib/workspace-cluster.d.ts +5 -7
  106. package/lib/workspace-cluster.d.ts.map +1 -1
  107. package/lib/workspace-cluster.js.map +1 -1
  108. package/lib/workspace-instance.d.ts +6 -0
  109. package/lib/workspace-instance.d.ts.map +1 -1
  110. package/lib/wsready.d.ts +3 -3
  111. package/lib/wsready.d.ts.map +1 -1
  112. package/lib/wsready.js +0 -2
  113. package/lib/wsready.js.map +1 -1
  114. package/package.json +12 -6
  115. package/pkg-yarn.lock +8 -5
  116. package/provenance-bundle.jsonl +1 -1
  117. package/src/accounting-protocol.ts +3 -0
  118. package/src/admin-protocol.ts +7 -0
  119. package/src/attribution.ts +48 -0
  120. package/src/billing-mode.ts +66 -0
  121. package/src/blocked-repositories-protocol.ts +13 -0
  122. package/src/context-url.ts +1 -1
  123. package/src/experiments/always-default.ts +24 -0
  124. package/src/experiments/configcat-server.ts +41 -0
  125. package/src/experiments/configcat.ts +58 -0
  126. package/src/experiments/types.ts +35 -0
  127. package/src/gitpod-service.ts +35 -2
  128. package/src/index.ts +1 -0
  129. package/src/installation-admin-protocol.ts +3 -0
  130. package/src/messaging/client-call-metrics.ts +1 -20
  131. package/src/messaging/error.ts +10 -4
  132. package/src/protocol.spec.ts +97 -0
  133. package/src/protocol.ts +144 -4
  134. package/src/team-subscription-protocol.ts +3 -0
  135. package/src/teams-projects-protocol.ts +14 -0
  136. package/src/usage.ts +50 -0
  137. package/src/util/debug-app.ts +81 -0
  138. package/src/util/gitpod-host-url.ts +21 -0
  139. package/src/util/grpc.ts +89 -0
  140. package/src/util/logging.ts +11 -2
  141. package/src/util/timeutil.ts +6 -0
  142. package/src/webhook-event.ts +55 -0
  143. package/src/workspace-class.ts +14 -0
  144. package/src/workspace-cluster.ts +2 -4
  145. package/src/workspace-instance.ts +11 -0
  146. package/src/wsready.ts +5 -4
package/src/protocol.ts CHANGED
@@ -45,6 +45,9 @@ export interface User {
45
45
  markedDeleted?: boolean;
46
46
 
47
47
  additionalData?: AdditionalUserData;
48
+
49
+ // Identifies an explicit team or user ID to which all the user's workspace usage should be attributed to (e.g. for billing purposes)
50
+ usageAttributionId?: string;
48
51
  }
49
52
 
50
53
  export namespace User {
@@ -70,11 +73,14 @@ export namespace User {
70
73
  }
71
74
 
72
75
  /**
73
- * Tries to return the primaryEmail of the first identity this user signed up with.
76
+ * Returns the stored email or if it doesn't exist returns the primaryEmail of the first identity this user signed up with.
74
77
  * @param user
75
78
  * @returns A primaryEmail, or undefined if there is none.
76
79
  */
77
80
  export function getPrimaryEmail(user: User): string | undefined {
81
+ if (user.additionalData?.profile?.emailAddress) {
82
+ return user.additionalData?.profile?.emailAddress;
83
+ }
78
84
  const identities = user.identities.filter((i) => !!i.primaryEmail);
79
85
  if (identities.length <= 0) {
80
86
  return undefined;
@@ -136,6 +142,50 @@ export namespace User {
136
142
  }
137
143
  user.additionalData.ideSettings = newIDESettings;
138
144
  }
145
+
146
+ export function getProfile(user: User): Profile {
147
+ return {
148
+ name: User.getName(user!) || "",
149
+ email: User.getPrimaryEmail(user!) || "",
150
+ company: user?.additionalData?.profile?.companyName,
151
+ avatarURL: user?.avatarUrl,
152
+ };
153
+ }
154
+
155
+ export function setProfile(user: User, profile: Profile): User {
156
+ user.fullName = profile.name;
157
+ user.avatarUrl = profile.avatarURL;
158
+
159
+ if (!user.additionalData) {
160
+ user.additionalData = {};
161
+ }
162
+ if (!user.additionalData.profile) {
163
+ user.additionalData.profile = {};
164
+ }
165
+ user.additionalData.profile.emailAddress = profile.email;
166
+ user.additionalData.profile.companyName = profile.company;
167
+ user.additionalData.profile.lastUpdatedDetailsNudge = new Date().toISOString();
168
+
169
+ return user;
170
+ }
171
+
172
+ // The actual Profile of a User
173
+ export interface Profile {
174
+ name: string;
175
+ email: string;
176
+ company?: string;
177
+ avatarURL?: string;
178
+ }
179
+ export namespace Profile {
180
+ export function hasChanges(before: Profile, after: Profile) {
181
+ return (
182
+ before.name !== after.name ||
183
+ before.email !== after.email ||
184
+ before.company !== after.company ||
185
+ before.avatarURL !== after.avatarURL
186
+ );
187
+ }
188
+ }
139
189
  }
140
190
 
141
191
  export interface AdditionalUserData {
@@ -150,9 +200,22 @@ export interface AdditionalUserData {
150
200
  oauthClientsApproved?: { [key: string]: string };
151
201
  // to remember GH Orgs the user installed/updated the GH App for
152
202
  knownGitHubOrgs?: string[];
153
-
154
203
  // Git clone URL pointing to the user's dotfile repo
155
204
  dotfileRepo?: string;
205
+ // preferred workspace classes
206
+ workspaceClasses?: WorkspaceClasses;
207
+ // additional user profile data
208
+ profile?: ProfileDetails;
209
+ }
210
+
211
+ // The format in which we store User Profiles in
212
+ export interface ProfileDetails {
213
+ // when was the last time the user updated their profile information or has been nudged to do so.
214
+ lastUpdatedDetailsNudge?: string;
215
+ // the user's company name
216
+ companyName?: string;
217
+ // the user's email
218
+ emailAddress?: string;
156
219
  }
157
220
 
158
221
  export interface EmailNotificationSettings {
@@ -171,6 +234,11 @@ export type IDESettings = {
171
234
  useLatestVersion?: boolean;
172
235
  };
173
236
 
237
+ export interface WorkspaceClasses {
238
+ regular: string;
239
+ prebuild: string;
240
+ }
241
+
174
242
  export interface UserPlatform {
175
243
  uid: string;
176
244
  userAgent: string;
@@ -203,8 +271,9 @@ export interface UserFeatureSettings {
203
271
  */
204
272
  export const WorkspaceFeatureFlags = {
205
273
  full_workspace_backup: undefined,
206
- fixed_resources: undefined,
207
274
  persistent_volume_claim: undefined,
275
+ protected_secrets: undefined,
276
+ workspace_class_limiting: undefined,
208
277
  };
209
278
  export type NamedWorkspaceFeatureFlag = keyof typeof WorkspaceFeatureFlags;
210
279
 
@@ -356,6 +425,68 @@ export namespace UserEnvVar {
356
425
  }
357
426
  }
358
427
 
428
+ export interface SSHPublicKeyValue {
429
+ name: string;
430
+ key: string;
431
+ }
432
+ export interface UserSSHPublicKey extends SSHPublicKeyValue {
433
+ id: string;
434
+ key: string;
435
+ userId: string;
436
+ fingerprint: string;
437
+ creationTime: string;
438
+ lastUsedTime?: string;
439
+ }
440
+
441
+ export type UserSSHPublicKeyValue = Omit<UserSSHPublicKey, "key" | "userId">;
442
+
443
+ export namespace SSHPublicKeyValue {
444
+ export function validate(value: SSHPublicKeyValue): string | undefined {
445
+ if (value.name.length === 0) {
446
+ return "Title must not be empty.";
447
+ }
448
+ if (value.name.length > 255) {
449
+ return "Title too long. Maximum value length is 255 characters.";
450
+ }
451
+ if (value.key.length === 0) {
452
+ return "Key must not be empty.";
453
+ }
454
+ try {
455
+ getData(value);
456
+ } catch (e) {
457
+ return "Key is invalid. You must supply a key in OpenSSH public key format.";
458
+ }
459
+ return;
460
+ }
461
+
462
+ export function getData(value: SSHPublicKeyValue) {
463
+ // Begins with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'.
464
+ const regex =
465
+ /^(?<type>ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519|sk-ecdsa-sha2-nistp256@openssh\.com|sk-ssh-ed25519@openssh\.com) (?<key>.*?)( (?<email>.*?))?$/;
466
+ const resultGroup = regex.exec(value.key.trim());
467
+ if (!resultGroup) {
468
+ throw new Error("Key is invalid.");
469
+ }
470
+ return {
471
+ type: resultGroup.groups?.["type"] as string,
472
+ key: resultGroup.groups?.["key"] as string,
473
+ email: resultGroup.groups?.["email"] || undefined,
474
+ };
475
+ }
476
+
477
+ export function getFingerprint(value: SSHPublicKeyValue) {
478
+ const data = getData(value);
479
+ let buf = Buffer.from(data.key, "base64");
480
+ // gitlab style
481
+ // const hash = createHash("md5").update(buf).digest("hex");
482
+ // github style
483
+ const hash = createHash("sha256").update(buf).digest("base64");
484
+ return hash;
485
+ }
486
+
487
+ export const MAXIMUM_KEY_LENGTH = 5;
488
+ }
489
+
359
490
  export interface GitpodToken {
360
491
  /** Hash value (SHA256) of the token (primary key). */
361
492
  tokenHash: string;
@@ -564,7 +695,7 @@ export interface Workspace {
564
695
 
565
696
  export type WorkspaceSoftDeletion = "user" | "gc";
566
697
 
567
- export type WorkspaceType = "regular" | "prebuild" | "probe";
698
+ export type WorkspaceType = "regular" | "prebuild" | "probe"; // TODO(gpl) Removed prove here
568
699
 
569
700
  export namespace Workspace {
570
701
  export function getFullRepositoryName(ws: Workspace): string | undefined {
@@ -640,6 +771,7 @@ export interface JetBrainsConfig {
640
771
  }
641
772
  export interface JetBrainsProductConfig {
642
773
  prebuilds?: JetBrainsPrebuilds;
774
+ vmoptions?: string;
643
775
  }
644
776
  export interface JetBrainsPrebuilds {
645
777
  version?: "stable" | "latest" | "both";
@@ -1350,3 +1482,11 @@ export interface Terms {
1350
1482
  readonly content: string;
1351
1483
  readonly formElements?: object;
1352
1484
  }
1485
+
1486
+ export interface CostCenter {
1487
+ readonly id: string;
1488
+ /**
1489
+ * Unit: credits
1490
+ */
1491
+ spendingLimit: number;
1492
+ }
@@ -55,6 +55,9 @@ export namespace TeamSubscription2 {
55
55
  export const isActive = (ts2: TeamSubscription2, date: string): boolean => {
56
56
  return ts2.startDate <= date && (ts2.endDate === undefined || date < ts2.endDate);
57
57
  };
58
+ export function isCancelled(s: TeamSubscription2, date: string): boolean {
59
+ return (!!s.cancellationDate && s.cancellationDate < date) || (!!s.endDate && s.endDate < date); // This edge case is meant to handle bad data: If for whatever reason cancellationDate has not been set: treat endDate as such
60
+ }
58
61
  }
59
62
 
60
63
  /**
@@ -7,6 +7,7 @@
7
7
  import { PrebuiltWorkspaceState } from "./protocol";
8
8
  import { v4 as uuidv4 } from "uuid";
9
9
  import { DeepPartial } from "./util/deep-partial";
10
+ import { WebhookEvent } from "./webhook-event";
10
11
 
11
12
  export interface ProjectConfig {
12
13
  ".gitpod.yml": string;
@@ -15,6 +16,7 @@ export interface ProjectConfig {
15
16
  export interface ProjectSettings {
16
17
  useIncrementalPrebuilds?: boolean;
17
18
  usePersistentVolumeClaim?: boolean;
19
+ keepOutdatedPrebuildsRunning?: boolean;
18
20
  }
19
21
 
20
22
  export interface Project {
@@ -151,3 +153,15 @@ export interface TeamMembershipInvite {
151
153
  /** This is a flag that triggers the HARD DELETION of this entity */
152
154
  deleted?: boolean;
153
155
  }
156
+
157
+ export interface PrebuildEvent {
158
+ id: string;
159
+ creationTime: string;
160
+ status: WebhookEvent.Status | WebhookEvent.PrebuildStatus;
161
+ message?: string;
162
+ prebuildId?: string;
163
+ projectId?: string;
164
+ cloneUrl?: string;
165
+ branch?: string;
166
+ commit?: string;
167
+ }
package/src/usage.ts ADDED
@@ -0,0 +1,50 @@
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 { WorkspaceType } from "./protocol";
8
+
9
+ export interface BillableSession {
10
+ // The id of the one paying the bill
11
+ attributionId: string;
12
+
13
+ // Relevant for workspace type. When prebuild, shows "prebuild"
14
+ userId?: string;
15
+ teamId?: string;
16
+
17
+ instanceId: string;
18
+
19
+ workspaceId: string;
20
+
21
+ workspaceType: BillableWorkspaceType;
22
+
23
+ workspaceClass: string;
24
+
25
+ // When the workspace started
26
+ startTime: string;
27
+
28
+ // When the workspace ended. Not set when the workspace is still running.
29
+ endTime?: string;
30
+
31
+ // The credits used for this session
32
+ credits: number;
33
+
34
+ // TODO - maybe
35
+ projectId?: string;
36
+ }
37
+
38
+ export interface BillableSessionRequest {
39
+ attributionId: string;
40
+ startedTimeOrder: SortOrder;
41
+ from?: number;
42
+ to?: number;
43
+ }
44
+
45
+ export type BillableWorkspaceType = WorkspaceType;
46
+
47
+ export enum SortOrder {
48
+ Descending = 0,
49
+ Ascending = 1,
50
+ }
@@ -0,0 +1,81 @@
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 * as http from "http";
8
+ import * as express from "express";
9
+ import { injectable, postConstruct } from "inversify";
10
+ import { log, LogrusLogLevel } from "./logging";
11
+
12
+ export interface SetLogLevelRequest {
13
+ level: LogrusLogLevel;
14
+ }
15
+ export namespace SetLogLevelRequest {
16
+ export function is(o: any): o is SetLogLevelRequest {
17
+ return typeof o === "object" && "level" in o;
18
+ }
19
+ }
20
+
21
+ @injectable()
22
+ export class DebugApp {
23
+ protected _app: express.Application;
24
+ protected httpServer: http.Server | undefined = undefined;
25
+
26
+ @postConstruct()
27
+ public ctor() {
28
+ this._app = this.create();
29
+ }
30
+
31
+ create(): express.Application {
32
+ const app = express();
33
+
34
+ app.use(express.json());
35
+ app.use(express.urlencoded({ extended: true }));
36
+
37
+ app.post("/debug/logging", (req, res) => {
38
+ try {
39
+ const levelRequest = req.body;
40
+ if (!SetLogLevelRequest.is(levelRequest)) {
41
+ res.status(400).end("not a SetLogLevelRequest");
42
+ return;
43
+ }
44
+
45
+ const newLogLevel = levelRequest.level;
46
+ log.setLogLevel(newLogLevel);
47
+ log.info("set log level", { newLogLevel });
48
+ res.status(200).end(JSON.stringify(levelRequest));
49
+ } catch (err) {
50
+ res.status(500).end(err);
51
+ }
52
+ });
53
+ return app;
54
+ }
55
+
56
+ public start(port: number = 6060) {
57
+ this.httpServer = this._app.listen(port, "localhost", () => {
58
+ log.info(`debug server listening on port: ${port}`);
59
+ });
60
+ }
61
+
62
+ public async stop() {
63
+ const server = this.httpServer;
64
+ if (!server) {
65
+ return;
66
+ }
67
+ return new Promise<void>((resolve) =>
68
+ server.close((err: any) => {
69
+ if (err) {
70
+ log.warn(`error while closing http server`, { err });
71
+ }
72
+ this.httpServer = undefined;
73
+ resolve();
74
+ }),
75
+ );
76
+ }
77
+
78
+ public get app(): express.Application {
79
+ return this._app;
80
+ }
81
+ }
@@ -85,6 +85,10 @@ export class GitpodHostUrl {
85
85
  return this.with((url) => ({ protocol: url.protocol === "https:" ? "wss:" : "ws:" }));
86
86
  }
87
87
 
88
+ asWorkspacePage(): GitpodHostUrl {
89
+ return this.with((url) => ({ pathname: "/workspaces" }));
90
+ }
91
+
88
92
  asDashboard(): GitpodHostUrl {
89
93
  return this.with((url) => ({ pathname: "/" }));
90
94
  }
@@ -171,4 +175,21 @@ export class GitpodHostUrl {
171
175
  asApiLogout(): GitpodHostUrl {
172
176
  return this.withApi((url) => ({ pathname: "/logout/" }));
173
177
  }
178
+
179
+ asIDEProxy(): GitpodHostUrl {
180
+ const hostSegments = this.url.host.split(".");
181
+ if (hostSegments[0] === "ide") {
182
+ return this;
183
+ }
184
+ return this.with((url) => ({ host: "ide." + url.host }));
185
+ }
186
+
187
+ asIDEMetrics(): GitpodHostUrl {
188
+ let newUrl: GitpodHostUrl = this;
189
+ const hostSegments = this.url.host.split(".");
190
+ if (hostSegments[0] !== "ide") {
191
+ newUrl = newUrl.asIDEProxy();
192
+ }
193
+ return newUrl.with((url) => ({ pathname: "/metrics-api" }));
194
+ }
174
195
  }
package/src/util/grpc.ts CHANGED
@@ -4,6 +4,9 @@
4
4
  * See License-AGPL.txt in the project root for license information.
5
5
  */
6
6
 
7
+ import * as grpc from "@grpc/grpc-js";
8
+ import { Status } from "@grpc/grpc-js/build/src/constants";
9
+
7
10
  export const defaultGRPCOptions = {
8
11
  "grpc.keepalive_timeout_ms": 10000,
9
12
  "grpc.keepalive_time_ms": 60000,
@@ -13,3 +16,89 @@ export const defaultGRPCOptions = {
13
16
  "grpc.max_reconnect_backoff_ms": 5000,
14
17
  "grpc.max_receive_message_length": 1024 * 1024 * 16,
15
18
  };
19
+
20
+ export type GrpcMethodType = "unary" | "client_stream" | "server_stream" | "bidi_stream";
21
+
22
+ export interface IGrpcCallMetricsLabels {
23
+ service: string;
24
+ method: string;
25
+ type: GrpcMethodType;
26
+ }
27
+
28
+ export interface IGrpcCallMetricsLabelsWithCode extends IGrpcCallMetricsLabels {
29
+ code: string;
30
+ }
31
+
32
+ export const IClientCallMetrics = Symbol("IClientCallMetrics");
33
+
34
+ export interface IClientCallMetrics {
35
+ started(labels: IGrpcCallMetricsLabels): void;
36
+ sent(labels: IGrpcCallMetricsLabels): void;
37
+ received(labels: IGrpcCallMetricsLabels): void;
38
+ handled(labels: IGrpcCallMetricsLabelsWithCode): void;
39
+ }
40
+
41
+ export function getGrpcMethodType(requestStream: boolean, responseStream: boolean): GrpcMethodType {
42
+ if (requestStream) {
43
+ if (responseStream) {
44
+ return "bidi_stream";
45
+ } else {
46
+ return "client_stream";
47
+ }
48
+ } else {
49
+ if (responseStream) {
50
+ return "server_stream";
51
+ } else {
52
+ return "unary";
53
+ }
54
+ }
55
+ }
56
+
57
+ export function createClientCallMetricsInterceptor(metrics: IClientCallMetrics): grpc.Interceptor {
58
+ return (options, nextCall): grpc.InterceptingCall => {
59
+ const methodDef = options.method_definition;
60
+ const method = methodDef.path.substring(methodDef.path.lastIndexOf("/") + 1);
61
+ const service = methodDef.path.substring(1, methodDef.path.length - method.length - 1);
62
+ const labels = {
63
+ service,
64
+ method,
65
+ type: getGrpcMethodType(options.method_definition.requestStream, options.method_definition.responseStream),
66
+ };
67
+ const requester = new grpc.RequesterBuilder()
68
+ .withStart((metadata, listener, next) => {
69
+ const newListener = new grpc.ListenerBuilder()
70
+ .withOnReceiveStatus((status, next) => {
71
+ try {
72
+ metrics.handled({
73
+ ...labels,
74
+ code: Status[status.code],
75
+ });
76
+ } finally {
77
+ next(status);
78
+ }
79
+ })
80
+ .withOnReceiveMessage((message, next) => {
81
+ try {
82
+ metrics.received(labels);
83
+ } finally {
84
+ next(message);
85
+ }
86
+ })
87
+ .build();
88
+ try {
89
+ metrics.started(labels);
90
+ } finally {
91
+ next(metadata, newListener);
92
+ }
93
+ })
94
+ .withSendMessage((message, next) => {
95
+ try {
96
+ metrics.sent(labels);
97
+ } finally {
98
+ next(message);
99
+ }
100
+ })
101
+ .build();
102
+ return new grpc.InterceptingCall(nextCall(options), requester);
103
+ };
104
+ }
@@ -6,6 +6,7 @@
6
6
 
7
7
  const inspect: (object: any) => string = require("util").inspect; // undefined in frontend
8
8
 
9
+ let plainLogging: boolean = false; // set to true during development to get non JSON output
9
10
  let jsonLogging: boolean = false;
10
11
  let component: string | undefined;
11
12
  let version: string | undefined;
@@ -309,7 +310,7 @@ function makeLogItem(
309
310
  }
310
311
 
311
312
  const payload: any = payloadArgs.length == 0 ? undefined : payloadArgs.length == 1 ? payloadArgs[0] : payloadArgs;
312
- const logItem: any = {
313
+ const logItem = {
313
314
  // undefined fields get eliminated in JSON.stringify()
314
315
  ...reportedErrorEvent,
315
316
  component,
@@ -321,11 +322,19 @@ function makeLogItem(
321
322
  payload,
322
323
  loggedViaConsole: calledViaConsole ? true : undefined,
323
324
  };
325
+ if (plainLogging) {
326
+ return `[${logItem.severity}] [${logItem.component}] ${logItem.message}
327
+ ${JSON.stringify(payload || "", undefined, " ")}
328
+ ${error || ""}
329
+ `.trim();
330
+ }
324
331
  let result: string = stringifyLogItem(logItem);
325
332
 
326
333
  if (result.length > maxAllowedLogItemLength && payload !== undefined) {
327
334
  delete logItem.payload;
328
- logItem.payloadStub = `Payload stripped as log item was longer than ${maxAllowedLogItemLength} characters`;
335
+ (<any>(
336
+ logItem
337
+ )).payloadStub = `Payload stripped as log item was longer than ${maxAllowedLogItemLength} characters`;
329
338
 
330
339
  result = stringifyLogItem(logItem);
331
340
 
@@ -47,6 +47,12 @@ export const orderAsc = (d1: string, d2: string): number => liftDate(d1, d2, (d1
47
47
  export const liftDate1 = <T>(d1: string, f: (d1: Date) => T): T => f(new Date(d1));
48
48
  export const liftDate = <T>(d1: string, d2: string, f: (d1: Date, d2: Date) => T): T => f(new Date(d1), new Date(d2));
49
49
 
50
+ export function hoursBefore(date: string, hours: number): string {
51
+ const result = new Date(date);
52
+ result.setHours(result.getHours() - hours);
53
+ return result.toISOString();
54
+ }
55
+
50
56
  export function hoursLater(date: string, hours: number): string {
51
57
  const result = new Date(date);
52
58
  result.setHours(result.getHours() + hours);
@@ -0,0 +1,55 @@
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 WebhookEvent {
8
+ id: string;
9
+ creationTime: string;
10
+ type: "push" | string;
11
+
12
+ /**
13
+ * Typically the webhook installer is referenced here.
14
+ */
15
+ authorizedUserId?: string;
16
+
17
+ /**
18
+ * webhook event's payload
19
+ */
20
+ rawEvent: string;
21
+
22
+ /**
23
+ * The general status of the received webhook event.
24
+ */
25
+ status: WebhookEvent.Status;
26
+
27
+ /**
28
+ * Optional message to help understand errors with handling events.
29
+ */
30
+ message?: string;
31
+
32
+ /**
33
+ * If the webhook event is considered to trigger a prebuild, the `prebuildStatus`
34
+ * contains a more specific status.
35
+ */
36
+ prebuildStatus?: WebhookEvent.PrebuildStatus;
37
+
38
+ /**
39
+ * If `prebuildStatus` is `prebuild_triggered` this points to a prebuild.
40
+ */
41
+ prebuildId?: string;
42
+
43
+ projectId?: string;
44
+
45
+ cloneUrl?: string;
46
+
47
+ branch?: string;
48
+
49
+ commit?: string;
50
+ }
51
+
52
+ export namespace WebhookEvent {
53
+ export type Status = "received" | "dismissed_unauthorized" | "ignored" | "processed";
54
+ export type PrebuildStatus = "ignored_unconfigured" | "prebuild_trigger_failed" | "prebuild_triggered";
55
+ }
@@ -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 SupportedWorkspaceClass {
8
+ id: string;
9
+ category: string;
10
+ displayName: string;
11
+ description: string;
12
+ powerups: number;
13
+ isDefault: boolean;
14
+ }
@@ -56,12 +56,10 @@ export type WorkspaceManagerConnectionInfo = Pick<WorkspaceCluster, "name" | "ur
56
56
  export type AdmissionConstraint =
57
57
  | AdmissionConstraintFeaturePreview
58
58
  | AdmissionConstraintHasPermission
59
- | AdmissionConstraintHasUserLevel
60
- | AdmissionConstraintHasMoreResources;
59
+ | AdmissionConstraintHasClass;
61
60
  export type AdmissionConstraintFeaturePreview = { type: "has-feature-preview" };
62
61
  export type AdmissionConstraintHasPermission = { type: "has-permission"; permission: PermissionName };
63
- export type AdmissionConstraintHasUserLevel = { type: "has-user-level"; level: string };
64
- export type AdmissionConstraintHasMoreResources = { type: "has-more-resources" };
62
+ export type AdmissionConstraintHasClass = { type: "has-class"; id: string; displayName: string };
65
63
 
66
64
  export namespace AdmissionConstraint {
67
65
  export function is(o: any): o is AdmissionConstraint {
@@ -62,10 +62,21 @@ export interface WorkspaceInstance {
62
62
  * resources that are provided to the workspace.
63
63
  */
64
64
  workspaceClass?: string;
65
+
66
+ /**
67
+ * Identifies the team or user to which this instance's runtime should be attributed to
68
+ * (e.g. for usage analytics or billing purposes).
69
+ */
70
+ usageAttributionId?: string;
65
71
  }
66
72
 
67
73
  // WorkspaceInstanceStatus describes the current state of a workspace instance
68
74
  export interface WorkspaceInstanceStatus {
75
+ // version is the current version of the workspace instance status
76
+ // Note: consider this value opague. The only guarantee given is that it imposes
77
+ // a partial order on status updates, i.e. a.version > b.version -> a newer than b.
78
+ version?: number;
79
+
69
80
  // phase describes a high-level state of the workspace instance
70
81
  phase: WorkspaceInstancePhase;
71
82