@gitpod/gitpod-protocol 0.1.5-sje-pacman.1 → 0.1.5-sje-kots-node-reqs.1

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 (139) hide show
  1. package/data/gitpod-schema.json +153 -2
  2. package/lib/accounting-protocol.d.ts +7 -0
  3. package/lib/accounting-protocol.d.ts.map +1 -1
  4. package/lib/accounting-protocol.js +8 -1
  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/blocked-repositories-protocol.d.ts +13 -0
  14. package/lib/blocked-repositories-protocol.d.ts.map +1 -0
  15. package/lib/blocked-repositories-protocol.js +8 -0
  16. package/lib/blocked-repositories-protocol.js.map +1 -0
  17. package/lib/context-url.js +1 -1
  18. package/lib/context-url.js.map +1 -1
  19. package/lib/env.d.ts +0 -5
  20. package/lib/env.d.ts.map +1 -1
  21. package/lib/env.js +1 -20
  22. package/lib/env.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 +8 -0
  28. package/lib/experiments/configcat-server.d.ts.map +1 -0
  29. package/lib/experiments/configcat-server.js +35 -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 +29 -5
  40. package/lib/gitpod-service.d.ts.map +1 -1
  41. package/lib/gitpod-service.js +20 -10
  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/license-protocol.d.ts +7 -0
  52. package/lib/license-protocol.d.ts.map +1 -1
  53. package/lib/license-protocol.js.map +1 -1
  54. package/lib/messaging/error.d.ts +2 -0
  55. package/lib/messaging/error.d.ts.map +1 -1
  56. package/lib/messaging/error.js +4 -0
  57. package/lib/messaging/error.js.map +1 -1
  58. package/lib/protocol.d.ts +86 -4
  59. package/lib/protocol.d.ts.map +1 -1
  60. package/lib/protocol.js +183 -12
  61. package/lib/protocol.js.map +1 -1
  62. package/lib/protocol.spec.d.ts +7 -0
  63. package/lib/protocol.spec.d.ts.map +1 -0
  64. package/lib/protocol.spec.js +127 -0
  65. package/lib/protocol.spec.js.map +1 -0
  66. package/lib/team-subscription-protocol.d.ts +16 -0
  67. package/lib/team-subscription-protocol.d.ts.map +1 -1
  68. package/lib/team-subscription-protocol.js +12 -1
  69. package/lib/team-subscription-protocol.js.map +1 -1
  70. package/lib/teams-projects-protocol.d.ts +18 -0
  71. package/lib/teams-projects-protocol.d.ts.map +1 -1
  72. package/lib/teams-projects-protocol.js.map +1 -1
  73. package/lib/usage.d.ts +22 -0
  74. package/lib/usage.d.ts.map +1 -0
  75. package/lib/usage.js +115 -0
  76. package/lib/usage.js.map +1 -0
  77. package/lib/util/debug-app.d.ts +25 -0
  78. package/lib/util/debug-app.d.ts.map +1 -0
  79. package/lib/util/debug-app.js +100 -0
  80. package/lib/util/debug-app.js.map +1 -0
  81. package/lib/util/generate-workspace-id.d.ts +5 -0
  82. package/lib/util/generate-workspace-id.d.ts.map +1 -1
  83. package/lib/util/generate-workspace-id.js +12 -8
  84. package/lib/util/generate-workspace-id.js.map +1 -1
  85. package/lib/util/generate-workspace-id.spec.js +10 -3
  86. package/lib/util/generate-workspace-id.spec.js.map +1 -1
  87. package/lib/util/logging.d.ts.map +1 -1
  88. package/lib/util/logging.js +9 -2
  89. package/lib/util/logging.js.map +1 -1
  90. package/lib/util/timeutil.d.ts +1 -0
  91. package/lib/util/timeutil.d.ts.map +1 -1
  92. package/lib/util/timeutil.js +7 -1
  93. package/lib/util/timeutil.js.map +1 -1
  94. package/lib/util/tracing.d.ts.map +1 -1
  95. package/lib/util/tracing.js +8 -10
  96. package/lib/util/tracing.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-cluster.d.ts +5 -7
  102. package/lib/workspace-cluster.d.ts.map +1 -1
  103. package/lib/workspace-cluster.js.map +1 -1
  104. package/lib/workspace-instance.d.ts +17 -1
  105. package/lib/workspace-instance.d.ts.map +1 -1
  106. package/lib/wsready.js +1 -1
  107. package/package.json +9 -6
  108. package/pkg-yarn.lock +7 -5
  109. package/provenance-bundle.jsonl +1 -1
  110. package/src/accounting-protocol.ts +10 -0
  111. package/src/admin-protocol.ts +7 -0
  112. package/src/attribution.ts +48 -0
  113. package/src/blocked-repositories-protocol.ts +13 -0
  114. package/src/context-url.ts +1 -1
  115. package/src/env.ts +0 -22
  116. package/src/experiments/always-default.ts +24 -0
  117. package/src/experiments/configcat-server.ts +38 -0
  118. package/src/experiments/configcat.ts +58 -0
  119. package/src/experiments/types.ts +35 -0
  120. package/src/gitpod-service.ts +54 -11
  121. package/src/index.ts +1 -0
  122. package/src/installation-admin-protocol.ts +3 -0
  123. package/src/license-protocol.ts +7 -0
  124. package/src/messaging/error.ts +6 -0
  125. package/src/protocol.spec.ts +97 -0
  126. package/src/protocol.ts +258 -13
  127. package/src/team-subscription-protocol.ts +24 -0
  128. package/src/teams-projects-protocol.ts +20 -0
  129. package/src/usage.ts +146 -0
  130. package/src/util/debug-app.ts +81 -0
  131. package/src/util/generate-workspace-id.spec.ts +10 -3
  132. package/src/util/generate-workspace-id.ts +9 -3
  133. package/src/util/logging.ts +11 -3
  134. package/src/util/timeutil.ts +6 -0
  135. package/src/util/tracing.ts +8 -10
  136. package/src/webhook-event.ts +55 -0
  137. package/src/workspace-cluster.ts +2 -4
  138. package/src/workspace-instance.ts +30 -1
  139. package/src/wsready.ts +1 -1
package/src/protocol.ts CHANGED
@@ -68,13 +68,22 @@ export namespace User {
68
68
  });
69
69
  return res;
70
70
  }
71
- export function getPrimaryEmail(user: User): string {
71
+
72
+ /**
73
+ * Returns the stored email or if it doesn't exist returns the primaryEmail of the first identity this user signed up with.
74
+ * @param user
75
+ * @returns A primaryEmail, or undefined if there is none.
76
+ */
77
+ export function getPrimaryEmail(user: User): string | undefined {
78
+ if (user.additionalData?.profile?.emailAddress) {
79
+ return user.additionalData?.profile?.emailAddress;
80
+ }
72
81
  const identities = user.identities.filter((i) => !!i.primaryEmail);
73
82
  if (identities.length <= 0) {
74
- throw new Error(`No identity with primary email for user: ${user.id}!`);
83
+ return undefined;
75
84
  }
76
85
 
77
- return identities[0].primaryEmail!;
86
+ return identities[0].primaryEmail || undefined;
78
87
  }
79
88
  export function getName(user: User): string | undefined {
80
89
  const name = user.fullName || user.name;
@@ -89,6 +98,91 @@ export namespace User {
89
98
  }
90
99
  return undefined;
91
100
  }
101
+
102
+ export function hasPreferredIde(user: User) {
103
+ return (
104
+ typeof user?.additionalData?.ideSettings?.defaultIde !== "undefined" ||
105
+ typeof user?.additionalData?.ideSettings?.useLatestVersion !== "undefined"
106
+ );
107
+ }
108
+
109
+ export function isOnboardingUser(user: User) {
110
+ return !hasPreferredIde(user);
111
+ }
112
+
113
+ export function migrationIDESettings(user: User) {
114
+ if (
115
+ !user?.additionalData?.ideSettings ||
116
+ Object.keys(user.additionalData.ideSettings).length === 0 ||
117
+ user.additionalData.ideSettings.settingVersion === "2.0"
118
+ ) {
119
+ return;
120
+ }
121
+ const newIDESettings: IDESettings = {
122
+ settingVersion: "2.0",
123
+ };
124
+ const ideSettings = user.additionalData.ideSettings;
125
+ if (ideSettings.useDesktopIde) {
126
+ if (ideSettings.defaultDesktopIde === "code-desktop") {
127
+ newIDESettings.defaultIde = "code-desktop";
128
+ } else if (ideSettings.defaultDesktopIde === "code-desktop-insiders") {
129
+ newIDESettings.defaultIde = "code-desktop";
130
+ newIDESettings.useLatestVersion = true;
131
+ } else {
132
+ newIDESettings.defaultIde = ideSettings.defaultDesktopIde;
133
+ newIDESettings.useLatestVersion = ideSettings.useLatestVersion;
134
+ }
135
+ } else {
136
+ const useLatest = ideSettings.defaultIde === "code-latest";
137
+ newIDESettings.defaultIde = "code";
138
+ newIDESettings.useLatestVersion = useLatest;
139
+ }
140
+ user.additionalData.ideSettings = newIDESettings;
141
+ }
142
+
143
+ export function getProfile(user: User): Profile {
144
+ return {
145
+ name: User.getName(user!) || "",
146
+ email: User.getPrimaryEmail(user!) || "",
147
+ company: user?.additionalData?.profile?.companyName,
148
+ avatarURL: user?.avatarUrl,
149
+ };
150
+ }
151
+
152
+ export function setProfile(user: User, profile: Profile): User {
153
+ user.fullName = profile.name;
154
+ user.avatarUrl = profile.avatarURL;
155
+
156
+ if (!user.additionalData) {
157
+ user.additionalData = {};
158
+ }
159
+ if (!user.additionalData.profile) {
160
+ user.additionalData.profile = {};
161
+ }
162
+ user.additionalData.profile.emailAddress = profile.email;
163
+ user.additionalData.profile.companyName = profile.company;
164
+ user.additionalData.profile.lastUpdatedDetailsNudge = new Date().toISOString();
165
+
166
+ return user;
167
+ }
168
+
169
+ // The actual Profile of a User
170
+ export interface Profile {
171
+ name: string;
172
+ email: string;
173
+ company?: string;
174
+ avatarURL?: string;
175
+ }
176
+ export namespace Profile {
177
+ export function hasChanges(before: Profile, after: Profile) {
178
+ return (
179
+ before.name !== after.name ||
180
+ before.email !== after.email ||
181
+ before.company !== after.company ||
182
+ before.avatarURL !== after.avatarURL
183
+ );
184
+ }
185
+ }
92
186
  }
93
187
 
94
188
  export interface AdditionalUserData {
@@ -103,9 +197,24 @@ export interface AdditionalUserData {
103
197
  oauthClientsApproved?: { [key: string]: string };
104
198
  // to remember GH Orgs the user installed/updated the GH App for
105
199
  knownGitHubOrgs?: string[];
106
-
107
200
  // Git clone URL pointing to the user's dotfile repo
108
201
  dotfileRepo?: string;
202
+ // Identifies an explicit team or user ID to which all the user's workspace usage should be attributed to (e.g. for billing purposes)
203
+ usageAttributionId?: string;
204
+ // preferred workspace classes
205
+ workspaceClasses?: WorkspaceClasses;
206
+ // additional user profile data
207
+ profile?: ProfileDetails;
208
+ }
209
+
210
+ // The format in which we store User Profiles in
211
+ export interface ProfileDetails {
212
+ // when was the last time the user updated their profile information or has been nudged to do so.
213
+ lastUpdatedDetailsNudge?: string;
214
+ // the user's company name
215
+ companyName?: string;
216
+ // the user's email
217
+ emailAddress?: string;
109
218
  }
110
219
 
111
220
  export interface EmailNotificationSettings {
@@ -115,12 +224,20 @@ export interface EmailNotificationSettings {
115
224
  }
116
225
 
117
226
  export type IDESettings = {
227
+ settingVersion?: string;
118
228
  defaultIde?: string;
229
+ // DEPRECATED: Use defaultIde after `settingVersion: 2.0`, no more specialify desktop or browser.
119
230
  useDesktopIde?: boolean;
231
+ // DEPRECATED: Same with useDesktopIde.
120
232
  defaultDesktopIde?: string;
121
233
  useLatestVersion?: boolean;
122
234
  };
123
235
 
236
+ export interface WorkspaceClasses {
237
+ regular: string;
238
+ prebuild: string;
239
+ }
240
+
124
241
  export interface UserPlatform {
125
242
  uid: string;
126
243
  userAgent: string;
@@ -151,7 +268,12 @@ export interface UserFeatureSettings {
151
268
  * The values of this type MUST MATCH enum values in WorkspaceFeatureFlag from ws-manager/client/core_pb.d.ts
152
269
  * If they don't we'll break things during workspace startup.
153
270
  */
154
- export const WorkspaceFeatureFlags = { full_workspace_backup: undefined, fixed_resources: undefined };
271
+ export const WorkspaceFeatureFlags = {
272
+ full_workspace_backup: undefined,
273
+ fixed_resources: undefined,
274
+ persistent_volume_claim: undefined,
275
+ protected_secrets: undefined,
276
+ };
155
277
  export type NamedWorkspaceFeatureFlag = keyof typeof WorkspaceFeatureFlags;
156
278
 
157
279
  export interface EnvVarWithValue {
@@ -178,6 +300,45 @@ export interface UserEnvVar extends UserEnvVarValue {
178
300
  }
179
301
 
180
302
  export namespace UserEnvVar {
303
+ /**
304
+ * @param variable
305
+ * @returns Either a string containing an error message or undefined.
306
+ */
307
+ export function validate(variable: UserEnvVarValue): string | undefined {
308
+ const name = variable.name;
309
+ const pattern = variable.repositoryPattern;
310
+ if (name.trim() === "") {
311
+ return "Name must not be empty.";
312
+ }
313
+ if (name.length > 255) {
314
+ return "Name too long. Maximum name length is 255 characters.";
315
+ }
316
+ if (!/^[a-zA-Z_]+[a-zA-Z0-9_]*$/.test(name)) {
317
+ return "Name must match /^[a-zA-Z_]+[a-zA-Z0-9_]*$/.";
318
+ }
319
+ if (variable.value.trim() === "") {
320
+ return "Value must not be empty.";
321
+ }
322
+ if (variable.value.length > 32767) {
323
+ return "Value too long. Maximum value length is 32767 characters.";
324
+ }
325
+ if (pattern.trim() === "") {
326
+ return "Scope must not be empty.";
327
+ }
328
+ const split = pattern.split("/");
329
+ if (split.length < 2) {
330
+ return "A scope must use the form 'organization/repo'.";
331
+ }
332
+ for (const name of split) {
333
+ if (name !== "*") {
334
+ if (!/^[a-zA-Z0-9_\-.\*]+$/.test(name)) {
335
+ return "Invalid scope segment. Only ASCII characters, numbers, -, _, . or * are allowed.";
336
+ }
337
+ }
338
+ }
339
+ return undefined;
340
+ }
341
+
181
342
  // DEPRECATED: Use ProjectEnvVar instead of repositoryPattern - https://github.com/gitpod-com/gitpod/issues/5322
182
343
  export function normalizeRepoPattern(pattern: string) {
183
344
  return pattern.toLocaleLowerCase();
@@ -263,6 +424,68 @@ export namespace UserEnvVar {
263
424
  }
264
425
  }
265
426
 
427
+ export interface SSHPublicKeyValue {
428
+ name: string;
429
+ key: string;
430
+ }
431
+ export interface UserSSHPublicKey extends SSHPublicKeyValue {
432
+ id: string;
433
+ key: string;
434
+ userId: string;
435
+ fingerprint: string;
436
+ creationTime: string;
437
+ lastUsedTime?: string;
438
+ }
439
+
440
+ export type UserSSHPublicKeyValue = Omit<UserSSHPublicKey, "key" | "userId">;
441
+
442
+ export namespace SSHPublicKeyValue {
443
+ export function validate(value: SSHPublicKeyValue): string | undefined {
444
+ if (value.name.length === 0) {
445
+ return "Title must not be empty.";
446
+ }
447
+ if (value.name.length > 255) {
448
+ return "Title too long. Maximum value length is 255 characters.";
449
+ }
450
+ if (value.key.length === 0) {
451
+ return "Key must not be empty.";
452
+ }
453
+ try {
454
+ getData(value);
455
+ } catch (e) {
456
+ return "Key is invalid. You must supply a key in OpenSSH public key format.";
457
+ }
458
+ return;
459
+ }
460
+
461
+ export function getData(value: SSHPublicKeyValue) {
462
+ // 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'.
463
+ const regex =
464
+ /^(?<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>.*?))?$/;
465
+ const resultGroup = regex.exec(value.key.trim());
466
+ if (!resultGroup) {
467
+ throw new Error("Key is invalid.");
468
+ }
469
+ return {
470
+ type: resultGroup.groups?.["type"] as string,
471
+ key: resultGroup.groups?.["key"] as string,
472
+ email: resultGroup.groups?.["email"] || undefined,
473
+ };
474
+ }
475
+
476
+ export function getFingerprint(value: SSHPublicKeyValue) {
477
+ const data = getData(value);
478
+ let buf = Buffer.from(data.key, "base64");
479
+ // gitlab style
480
+ // const hash = createHash("md5").update(buf).digest("hex");
481
+ // github style
482
+ const hash = createHash("sha256").update(buf).digest("base64");
483
+ return hash;
484
+ }
485
+
486
+ export const MAXIMUM_KEY_LENGTH = 5;
487
+ }
488
+
266
489
  export interface GitpodToken {
267
490
  /** Hash value (SHA256) of the token (primary key). */
268
491
  tokenHash: string;
@@ -395,6 +618,13 @@ export interface Snapshot {
395
618
  message?: string;
396
619
  }
397
620
 
621
+ export interface VolumeSnapshot {
622
+ id: string;
623
+ workspaceId: string;
624
+ creationTime: string;
625
+ volumeHandle: string;
626
+ }
627
+
398
628
  export type SnapshotState = "pending" | "available" | "error";
399
629
 
400
630
  export interface LayoutData {
@@ -464,7 +694,7 @@ export interface Workspace {
464
694
 
465
695
  export type WorkspaceSoftDeletion = "user" | "gc";
466
696
 
467
- export type WorkspaceType = "regular" | "prebuild" | "probe";
697
+ export type WorkspaceType = "regular" | "prebuild" | "probe"; // TODO(gpl) Removed prove here
468
698
 
469
699
  export namespace Workspace {
470
700
  export function getFullRepositoryName(ws: Workspace): string | undefined {
@@ -532,6 +762,20 @@ export interface VSCodeConfig {
532
762
  extensions?: string[];
533
763
  }
534
764
 
765
+ export interface JetBrainsConfig {
766
+ intellij?: JetBrainsProductConfig;
767
+ goland?: JetBrainsProductConfig;
768
+ pycharm?: JetBrainsProductConfig;
769
+ phpstorm?: JetBrainsProductConfig;
770
+ }
771
+ export interface JetBrainsProductConfig {
772
+ prebuilds?: JetBrainsPrebuilds;
773
+ vmoptions?: string;
774
+ }
775
+ export interface JetBrainsPrebuilds {
776
+ version?: "stable" | "latest" | "both";
777
+ }
778
+
535
779
  export interface RepositoryCloneInformation {
536
780
  url: string;
537
781
  checkoutLocation?: string;
@@ -548,6 +792,7 @@ export interface WorkspaceConfig {
548
792
  gitConfig?: { [config: string]: string };
549
793
  github?: GithubAppConfig;
550
794
  vscode?: VSCodeConfig;
795
+ jetbrains?: JetBrainsConfig;
551
796
 
552
797
  /** deprecated. Enabled by default **/
553
798
  experimentalNetwork?: boolean;
@@ -634,13 +879,16 @@ export interface PrebuiltWorkspace {
634
879
  buildWorkspaceId: string;
635
880
  creationTime: string;
636
881
  state: PrebuiltWorkspaceState;
882
+ statusVersion: number;
637
883
  error?: string;
638
884
  snapshot?: string;
639
885
  }
640
886
 
641
887
  export namespace PrebuiltWorkspace {
642
888
  export function isDone(pws: PrebuiltWorkspace) {
643
- return pws.state === "available" || pws.state === "timeout" || pws.state === "aborted";
889
+ return (
890
+ pws.state === "available" || pws.state === "timeout" || pws.state === "aborted" || pws.state === "failed"
891
+ );
644
892
  }
645
893
 
646
894
  export function isAvailable(pws: PrebuiltWorkspace) {
@@ -764,6 +1012,7 @@ export namespace ExternalImageConfigFile {
764
1012
 
765
1013
  export interface WorkspaceContext {
766
1014
  title: string;
1015
+ ref?: string;
767
1016
  /** This contains the URL portion of the contextURL (which might contain other modifiers as well). It's optional because it's not set for older workspaces. */
768
1017
  normalizedContextURL?: string;
769
1018
  forceCreateNewWorkspace?: boolean;
@@ -1015,6 +1264,8 @@ export interface Repository {
1015
1264
  owner: string;
1016
1265
  name: string;
1017
1266
  cloneUrl: string;
1267
+ /* Optional kind to differentiate between repositories of orgs/groups/projects and personal repos. */
1268
+ repoKind?: string;
1018
1269
  description?: string;
1019
1270
  avatarUrl?: string;
1020
1271
  webUrl?: string;
@@ -1143,12 +1394,6 @@ export interface AuthProviderInfo {
1143
1394
  };
1144
1395
  }
1145
1396
 
1146
- export namespace AuthProviderInfo {
1147
- export function isGitHubEnterprise(info?: AuthProviderInfo): boolean {
1148
- return !!info && info.authProviderType === "GitHub" && info.host !== "github.com";
1149
- }
1150
- }
1151
-
1152
1397
  export interface AuthProviderEntry {
1153
1398
  readonly id: string;
1154
1399
  readonly type: AuthProviderEntry.Type;
@@ -33,6 +33,30 @@ export namespace TeamSubscription {
33
33
  };
34
34
  }
35
35
 
36
+ export interface TeamSubscription2 {
37
+ id: string;
38
+ teamId: string;
39
+ planId: string;
40
+ startDate: string;
41
+ endDate?: string;
42
+ quantity: number;
43
+ /** The Chargebee subscription id */
44
+ paymentReference: string;
45
+ cancellationDate?: string;
46
+ excludeFromMoreResources: boolean;
47
+ }
48
+
49
+ export namespace TeamSubscription2 {
50
+ export const create = (ts2: Omit<TeamSubscription2, "id">): TeamSubscription2 => {
51
+ const withId = ts2 as TeamSubscription2;
52
+ withId.id = uuidv4();
53
+ return withId;
54
+ };
55
+ export const isActive = (ts2: TeamSubscription2, date: string): boolean => {
56
+ return ts2.startDate <= date && (ts2.endDate === undefined || date < ts2.endDate);
57
+ };
58
+ }
59
+
36
60
  /**
37
61
  * A slot represents one unit of a TeamSubscription that gets assigned to one user at a time
38
62
  */
@@ -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;
@@ -14,6 +15,8 @@ export interface ProjectConfig {
14
15
 
15
16
  export interface ProjectSettings {
16
17
  useIncrementalPrebuilds?: boolean;
18
+ usePersistentVolumeClaim?: boolean;
19
+ keepOutdatedPrebuildsRunning?: boolean;
17
20
  }
18
21
 
19
22
  export interface Project {
@@ -69,6 +72,11 @@ export namespace Project {
69
72
 
70
73
  export type PartialProject = DeepPartial<Project> & Pick<Project, "id">;
71
74
 
75
+ export interface ProjectUsage {
76
+ lastWebhookReceived: string;
77
+ lastWorkspaceStart: string;
78
+ }
79
+
72
80
  export interface PrebuildWithStatus {
73
81
  info: PrebuildInfo;
74
82
  status: PrebuiltWorkspaceState;
@@ -145,3 +153,15 @@ export interface TeamMembershipInvite {
145
153
  /** This is a flag that triggers the HARD DELETION of this entity */
146
154
  deleted?: boolean;
147
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,146 @@
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
+ // "standard" or "XL"
24
+ workspaceClass: string;
25
+
26
+ // When the workspace started
27
+ startTime: string;
28
+
29
+ // When the workspace ended
30
+ endTime: string;
31
+
32
+ // The credits used for this session
33
+ credits: number;
34
+
35
+ // TODO - maybe
36
+ projectId?: string;
37
+ }
38
+
39
+ export type BillableWorkspaceType = WorkspaceType;
40
+
41
+ export const billableSessionDummyData: BillableSession[] = [
42
+ {
43
+ attributionId: "some-attribution-id",
44
+ userId: "prebuild",
45
+ teamId: "prebuild",
46
+ instanceId: "some-instance-id",
47
+ workspaceId: "some-workspace-id",
48
+ workspaceType: "prebuild",
49
+ workspaceClass: "XL",
50
+ startTime: new Date(Date.now() + -3 * 24 * 3600 * 1000).toISOString(), // 3 days ago
51
+ endTime: new Date().toISOString(),
52
+ credits: 320,
53
+ projectId: "project-123",
54
+ },
55
+ {
56
+ attributionId: "some-attribution-id2",
57
+ userId: "some-user",
58
+ teamId: "some-team",
59
+ instanceId: "some-instance-id2",
60
+ workspaceId: "some-workspace-id2",
61
+ workspaceType: "regular",
62
+ workspaceClass: "standard",
63
+ startTime: new Date(Date.now() + -5 * 24 * 3600 * 1000).toISOString(),
64
+ endTime: new Date().toISOString(),
65
+ credits: 130,
66
+ projectId: "project-123",
67
+ },
68
+ {
69
+ attributionId: "some-attribution-id3",
70
+ userId: "some-other-user",
71
+ teamId: "some-other-team",
72
+ instanceId: "some-instance-id3",
73
+ workspaceId: "some-workspace-id3",
74
+ workspaceType: "regular",
75
+ workspaceClass: "XL",
76
+ startTime: new Date(Date.now() + -5 * 24 * 3600 * 1000).toISOString(),
77
+ endTime: new Date(Date.now() + -4 * 24 * 3600 * 1000).toISOString(),
78
+ credits: 150,
79
+ projectId: "project-134",
80
+ },
81
+ {
82
+ attributionId: "some-attribution-id4",
83
+ userId: "some-other-user2",
84
+ teamId: "some-other-team2",
85
+ instanceId: "some-instance-id4",
86
+ workspaceId: "some-workspace-id4",
87
+ workspaceType: "regular",
88
+ workspaceClass: "standard",
89
+ startTime: new Date(Date.now() + -10 * 24 * 3600 * 1000).toISOString(),
90
+ endTime: new Date(Date.now() + -9 * 24 * 3600 * 1000).toISOString(),
91
+ credits: 330,
92
+ projectId: "project-137",
93
+ },
94
+ {
95
+ attributionId: "some-attribution-id5",
96
+ userId: "some-other-user3",
97
+ teamId: "some-other-team3",
98
+ instanceId: "some-instance-id5",
99
+ workspaceId: "some-workspace-id5",
100
+ workspaceType: "regular",
101
+ workspaceClass: "XL",
102
+ startTime: new Date(Date.now() + -2 * 24 * 3600 * 1000).toISOString(),
103
+ endTime: new Date().toISOString(),
104
+ credits: 222,
105
+ projectId: "project-138",
106
+ },
107
+ {
108
+ attributionId: "some-attribution-id6",
109
+ userId: "some-other-user4",
110
+ teamId: "some-other-team4",
111
+ instanceId: "some-instance-id6",
112
+ workspaceId: "some-workspace-id3",
113
+ workspaceType: "regular",
114
+ workspaceClass: "XL",
115
+ startTime: new Date(Date.now() + -7 * 24 * 3600 * 1000).toISOString(),
116
+ endTime: new Date(Date.now() + -6 * 24 * 3600 * 1000).toISOString(),
117
+ credits: 300,
118
+ projectId: "project-134",
119
+ },
120
+ {
121
+ attributionId: "some-attribution-id8",
122
+ userId: "some-other-user5",
123
+ teamId: "some-other-team5",
124
+ instanceId: "some-instance-id8",
125
+ workspaceId: "some-workspace-id3",
126
+ workspaceType: "regular",
127
+ workspaceClass: "standard",
128
+ startTime: new Date(Date.now() + -1 * 24 * 3600 * 1000).toISOString(),
129
+ endTime: new Date().toISOString(),
130
+ credits: 100,
131
+ projectId: "project-567",
132
+ },
133
+ {
134
+ attributionId: "some-attribution-id7",
135
+ userId: "prebuild",
136
+ teamId: "some-other-team7",
137
+ instanceId: "some-instance-id7",
138
+ workspaceId: "some-workspace-id7",
139
+ workspaceType: "prebuild",
140
+ workspaceClass: "XL",
141
+ startTime: new Date(Date.now() + -1 * 24 * 3600 * 1000).toISOString(),
142
+ endTime: new Date().toISOString(),
143
+ credits: 200,
144
+ projectId: "project-345",
145
+ },
146
+ ];
@@ -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
+ }