mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
* [supervisor] add new .gitpod.yml on-port option `ignore-completely` * fixup * fix serve * fixup
1559 lines
47 KiB
TypeScript
1559 lines
47 KiB
TypeScript
/**
|
|
* Copyright (c) 2020 Gitpod GmbH. All rights reserved.
|
|
* Licensed under the GNU Affero General Public License (AGPL).
|
|
* See License.AGPL.txt in the project root for license information.
|
|
*/
|
|
|
|
import { WorkspaceInstance, PortVisibility, PortProtocol, WorkspaceInstanceMetrics } from "./workspace-instance";
|
|
import { RoleOrPermission } from "./permission";
|
|
import { Project } from "./teams-projects-protocol";
|
|
import { createHash } from "crypto";
|
|
import { WorkspaceRegion } from "./workspace-cluster";
|
|
|
|
export interface UserInfo {
|
|
name?: string;
|
|
}
|
|
|
|
export interface User {
|
|
/** The user id */
|
|
id: string;
|
|
|
|
/** The ID of the Organization this user is owned by. If undefined, the user is owned by the installation */
|
|
organizationId?: string;
|
|
|
|
/** The timestamp when the user entry was created */
|
|
creationDate: string;
|
|
|
|
avatarUrl?: string;
|
|
|
|
name?: string;
|
|
|
|
/** Optional for backwards compatibility */
|
|
fullName?: string;
|
|
|
|
identities: Identity[];
|
|
|
|
/**
|
|
* Whether the user has been blocked to use our service, because of TOS violation for example.
|
|
* Optional for backwards compatibility.
|
|
*/
|
|
blocked?: boolean;
|
|
|
|
/** A map of random settings that alter the behaviour of Gitpod on a per-user basis */
|
|
featureFlags?: UserFeatureSettings;
|
|
|
|
/** The permissions and/or roles the user has */
|
|
rolesOrPermissions?: RoleOrPermission[];
|
|
|
|
/** Whether the user is logical deleted. This flag is respected by all queries in UserDB */
|
|
markedDeleted?: boolean;
|
|
|
|
additionalData?: AdditionalUserData;
|
|
|
|
// Identifies an explicit team or user ID to which all the user's workspace usage should be attributed to (e.g. for billing purposes)
|
|
usageAttributionId?: string;
|
|
|
|
// The last time this user got verified somehow. The user is not verified if this is empty.
|
|
lastVerificationTime?: string;
|
|
|
|
// The phone number used for the last phone verification.
|
|
verificationPhoneNumber?: string;
|
|
|
|
// The FGA relationships version of this user
|
|
fgaRelationshipsVersion?: number;
|
|
}
|
|
|
|
export namespace User {
|
|
export function is(data: any): data is User {
|
|
return data && data.hasOwnProperty("id") && data.hasOwnProperty("identities");
|
|
}
|
|
export function getIdentity(user: User, authProviderId: string): Identity | undefined {
|
|
return user.identities.find((id) => id.authProviderId === authProviderId);
|
|
}
|
|
|
|
export function isOrganizationOwned(user: User) {
|
|
return !!user.organizationId;
|
|
}
|
|
}
|
|
|
|
export interface WorkspaceTimeoutSetting {
|
|
// user globol workspace timeout
|
|
workspaceTimeout: string;
|
|
// control whether to enable the closed timeout of a workspace, i.e. close web ide, disconnect ssh connection
|
|
disabledClosedTimeout: boolean;
|
|
}
|
|
|
|
export interface AdditionalUserData extends Partial<WorkspaceTimeoutSetting> {
|
|
/** @deprecated unused */
|
|
platforms?: UserPlatform[];
|
|
emailNotificationSettings?: EmailNotificationSettings;
|
|
featurePreview?: boolean;
|
|
ideSettings?: IDESettings;
|
|
// key is the name of the news, string the iso date when it was seen
|
|
whatsNewSeen?: { [key: string]: string };
|
|
// key is the name of the OAuth client i.e. local app, string the iso date when it was approved
|
|
// TODO(rl): provide a management UX to allow rescinding of approval
|
|
oauthClientsApproved?: { [key: string]: string };
|
|
// to remember GH Orgs the user installed/updated the GH App for
|
|
/** @deprecated unused */
|
|
knownGitHubOrgs?: string[];
|
|
// Git clone URL pointing to the user's dotfile repo
|
|
dotfileRepo?: string;
|
|
// preferred workspace classes
|
|
workspaceClasses?: WorkspaceClasses;
|
|
// additional user profile data
|
|
profile?: ProfileDetails;
|
|
/** @deprecated */
|
|
shouldSeeMigrationMessage?: boolean;
|
|
// remembered workspace auto start options
|
|
workspaceAutostartOptions?: WorkspaceAutostartOption[];
|
|
}
|
|
|
|
export interface WorkspaceAutostartOption {
|
|
cloneURL: string;
|
|
organizationId: string;
|
|
workspaceClass?: string;
|
|
ideSettings?: IDESettings;
|
|
region?: WorkspaceRegion;
|
|
}
|
|
|
|
export namespace AdditionalUserData {
|
|
export function set(user: User, partialData: Partial<AdditionalUserData>): User {
|
|
if (!user.additionalData) {
|
|
user.additionalData = {
|
|
...partialData,
|
|
};
|
|
} else {
|
|
user.additionalData = {
|
|
...user.additionalData,
|
|
...partialData,
|
|
};
|
|
}
|
|
return user;
|
|
}
|
|
}
|
|
|
|
// The format in which we store User Profiles in
|
|
export interface ProfileDetails {
|
|
// when was the last time the user updated their profile information or has been nudged to do so.
|
|
lastUpdatedDetailsNudge?: string;
|
|
// when was the last time the user has accepted our privacy policy
|
|
acceptedPrivacyPolicyDate?: string;
|
|
// the user's company name
|
|
companyName?: string;
|
|
// the user's email
|
|
emailAddress?: string;
|
|
// type of role user has in their job
|
|
jobRole?: string;
|
|
// freeform entry for job role user works in (when jobRole is "other")
|
|
jobRoleOther?: string;
|
|
// Reasons user is exploring Gitpod when they signed up
|
|
explorationReasons?: string[];
|
|
// what user hopes to accomplish when they signed up
|
|
signupGoals?: string[];
|
|
// freeform entry for signup goals (when signupGoals is "other")
|
|
signupGoalsOther?: string;
|
|
// Set after a user completes the onboarding flow
|
|
onboardedTimestamp?: string;
|
|
// Onboarding question about a user's company size
|
|
companySize?: string;
|
|
// key-value pairs of dialogs in the UI which should only appear once. The value usually is a timestamp of the last dismissal
|
|
coachmarksDismissals?: { [key: string]: string };
|
|
}
|
|
|
|
export interface EmailNotificationSettings {
|
|
allowsChangelogMail?: boolean;
|
|
allowsDevXMail?: boolean;
|
|
allowsOnboardingMail?: boolean;
|
|
}
|
|
|
|
export type IDESettings = {
|
|
settingVersion?: string;
|
|
defaultIde?: string;
|
|
useLatestVersion?: boolean;
|
|
preferToolbox?: boolean;
|
|
// DEPRECATED: Use defaultIde after `settingVersion: 2.0`, no more specialify desktop or browser.
|
|
useDesktopIde?: boolean;
|
|
// DEPRECATED: Same with useDesktopIde.
|
|
defaultDesktopIde?: string;
|
|
};
|
|
|
|
export interface WorkspaceClasses {
|
|
regular?: string;
|
|
/**
|
|
* @deprecated see Project.settings.prebuilds.workspaceClass
|
|
*/
|
|
prebuild?: string;
|
|
}
|
|
|
|
export interface UserPlatform {
|
|
uid: string;
|
|
userAgent: string;
|
|
browser: string;
|
|
os: string;
|
|
lastUsed: string;
|
|
firstUsed: string;
|
|
/**
|
|
* Since when does the user have the browser extension installe don this device.
|
|
*/
|
|
browserExtensionInstalledSince?: string;
|
|
|
|
/**
|
|
* Since when does the user not have the browser extension installed anymore (but previously had).
|
|
*/
|
|
browserExtensionUninstalledSince?: string;
|
|
}
|
|
|
|
export interface UserFeatureSettings {
|
|
/**
|
|
* Permanent feature flags are added to each and every workspace instance
|
|
* this user starts.
|
|
*/
|
|
permanentWSFeatureFlags?: NamedWorkspaceFeatureFlag[];
|
|
}
|
|
|
|
export type BillingTier = "paid" | "free";
|
|
|
|
/**
|
|
* The values of this type MUST MATCH enum values in WorkspaceFeatureFlag from ws-manager/client/core_pb.d.ts
|
|
* If they don't we'll break things during workspace startup.
|
|
*/
|
|
export const WorkspaceFeatureFlags = {
|
|
full_workspace_backup: undefined,
|
|
workspace_class_limiting: undefined,
|
|
workspace_connection_limiting: undefined,
|
|
workspace_psi: undefined,
|
|
ssh_ca: undefined,
|
|
};
|
|
export type NamedWorkspaceFeatureFlag = keyof typeof WorkspaceFeatureFlags;
|
|
export namespace NamedWorkspaceFeatureFlag {
|
|
export const WORKSPACE_PERSISTED_FEATTURE_FLAGS: NamedWorkspaceFeatureFlag[] = ["full_workspace_backup"];
|
|
export function isWorkspacePersisted(ff: NamedWorkspaceFeatureFlag): boolean {
|
|
return WORKSPACE_PERSISTED_FEATTURE_FLAGS.includes(ff);
|
|
}
|
|
}
|
|
|
|
export type EnvVar = UserEnvVar | ProjectEnvVarWithValue | OrgEnvVarWithValue | EnvVarWithValue;
|
|
|
|
export interface EnvVarWithValue {
|
|
name: string;
|
|
value: string;
|
|
}
|
|
|
|
export interface ProjectEnvVarWithValue extends EnvVarWithValue {
|
|
id?: string;
|
|
/** If a project-scoped env var is "censored", it is only visible in Prebuilds */
|
|
censored: boolean;
|
|
}
|
|
|
|
export interface ProjectEnvVar extends Omit<ProjectEnvVarWithValue, "value"> {
|
|
id: string;
|
|
projectId: string;
|
|
}
|
|
|
|
export interface OrgEnvVarWithValue extends EnvVarWithValue {
|
|
id?: string;
|
|
}
|
|
|
|
export interface OrgEnvVar extends Omit<OrgEnvVarWithValue, "value"> {
|
|
id: string;
|
|
orgId: string;
|
|
}
|
|
|
|
export interface UserEnvVarValue extends EnvVarWithValue {
|
|
id?: string;
|
|
repositoryPattern: string; // DEPRECATED: Use ProjectEnvVar instead of repositoryPattern - https://github.com/gitpod-com/gitpod/issues/5322
|
|
}
|
|
export interface UserEnvVar extends UserEnvVarValue {
|
|
id: string;
|
|
userId: string;
|
|
deleted?: boolean;
|
|
}
|
|
|
|
export namespace EnvVar {
|
|
export const GITPOD_IMAGE_AUTH_ENV_VAR_NAME = "GITPOD_IMAGE_AUTH";
|
|
/**
|
|
* - GITPOD_IMAGE_AUTH is documented https://www.gitpod.io/docs/configure/workspaces/workspace-image#use-a-private-docker-image
|
|
*/
|
|
export const WhiteListFromReserved = [GITPOD_IMAGE_AUTH_ENV_VAR_NAME];
|
|
|
|
export function is(data: any): data is EnvVar {
|
|
return data.hasOwnProperty("name") && data.hasOwnProperty("value");
|
|
}
|
|
|
|
/**
|
|
* Extracts the "host:credentials" pairs from the GITPOD_IMAGE_AUTH environment variable.
|
|
* @param envVars
|
|
* @returns A map of host to credentials
|
|
*/
|
|
export function getGitpodImageAuth(envVars: EnvVarWithValue[]): Map<string, string> {
|
|
const res = new Map<string, string>();
|
|
const imageAuth = envVars.find((e) => e.name === EnvVar.GITPOD_IMAGE_AUTH_ENV_VAR_NAME);
|
|
if (!imageAuth) {
|
|
return res;
|
|
}
|
|
|
|
const parse = (parts: string[]): { host: string; auth: string } | undefined => {
|
|
if (parts.some((e) => e === "")) {
|
|
return undefined;
|
|
}
|
|
if (parts.length === 2) {
|
|
return { host: parts[0], auth: parts[1] };
|
|
} else if (parts.length === 3) {
|
|
return { host: `${parts[0]}:${parts[1]}`, auth: parts[2] };
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
(imageAuth.value || "")
|
|
.split(",")
|
|
.map((e) => e.split(":").map((part) => part.trim()))
|
|
.forEach((parts) => {
|
|
const parsed = parse(parts);
|
|
if (parsed) {
|
|
res.set(parsed.host, parsed.auth);
|
|
}
|
|
});
|
|
|
|
return res;
|
|
}
|
|
}
|
|
|
|
export namespace UserEnvVar {
|
|
export const DELIMITER = "/";
|
|
export const WILDCARD_ASTERISK = "*";
|
|
// This wildcard is only allowed on the last segment, and matches arbitrary segments to the right
|
|
export const WILDCARD_DOUBLE_ASTERISK = "**";
|
|
const WILDCARD_SHARP = "#"; // TODO(gpl) Where does this come from? Bc we have/had patterns as part of URLs somewhere, maybe...?
|
|
const MIN_PATTERN_SEGMENTS = 2;
|
|
|
|
function isWildcard(c: string): boolean {
|
|
return c === WILDCARD_ASTERISK || c === WILDCARD_SHARP;
|
|
}
|
|
|
|
export function is(data: any): data is UserEnvVar {
|
|
return (
|
|
EnvVar.is(data) &&
|
|
data.hasOwnProperty("id") &&
|
|
data.hasOwnProperty("userId") &&
|
|
data.hasOwnProperty("repositoryPattern")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param variable
|
|
* @returns Either a string containing an error message or undefined.
|
|
*/
|
|
export function validate(variable: UserEnvVarValue): string | undefined {
|
|
const name = variable.name;
|
|
const pattern = variable.repositoryPattern;
|
|
if (!EnvVar.WhiteListFromReserved.includes(name) && name.startsWith("GITPOD_")) {
|
|
return "Name with prefix 'GITPOD_' is reserved.";
|
|
}
|
|
if (name.trim() === "") {
|
|
return "Name must not be empty.";
|
|
}
|
|
if (name.length > 255) {
|
|
return "Name too long. Maximum name length is 255 characters.";
|
|
}
|
|
if (!/^[a-zA-Z_]+[a-zA-Z0-9_]*$/.test(name)) {
|
|
return "Name must match /^[a-zA-Z_]+[a-zA-Z0-9_]*$/.";
|
|
}
|
|
if (variable.value.trim() === "") {
|
|
return "Value must not be empty.";
|
|
}
|
|
if (variable.value.length > 32767) {
|
|
return "Value too long. Maximum value length is 32767 characters.";
|
|
}
|
|
if (pattern.trim() === "") {
|
|
return "Scope must not be empty.";
|
|
}
|
|
const split = splitRepositoryPattern(pattern);
|
|
if (split.length < MIN_PATTERN_SEGMENTS) {
|
|
return "A scope must use the form 'organization/repo'.";
|
|
}
|
|
for (const name of split) {
|
|
if (name !== "*") {
|
|
if (!/^[a-zA-Z0-9_\-.\*]+$/.test(name)) {
|
|
return "Invalid scope segment. Only ASCII characters, numbers, -, _, . or * are allowed.";
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function normalizeRepoPattern(pattern: string) {
|
|
return pattern.toLocaleLowerCase();
|
|
}
|
|
|
|
function splitRepositoryPattern(pattern: string): string[] {
|
|
return pattern.split(DELIMITER);
|
|
}
|
|
|
|
function joinRepositoryPattern(...parts: string[]): string {
|
|
return parts.join(DELIMITER);
|
|
}
|
|
|
|
/**
|
|
* Matches the given EnvVar pattern against the fully qualified name of the repository:
|
|
* - GitHub: "repo/owner"
|
|
* - GitLab: "some/nested/repo" (up to MAX_PATTERN_SEGMENTS deep)
|
|
* @param pattern
|
|
* @param repoFqn
|
|
* @returns True if the pattern matches that fully qualified name
|
|
*/
|
|
export function matchEnvVarPattern(pattern: string, repoFqn: string): boolean {
|
|
const fqnSegments = splitRepositoryPattern(repoFqn);
|
|
const patternSegments = splitRepositoryPattern(pattern);
|
|
if (patternSegments.length < MIN_PATTERN_SEGMENTS) {
|
|
// Technically not a problem, but we should not allow this for safety reasons.
|
|
// And because we hisotrically never allowed this (GitHub would always require at least "*/*") we are just keeping old constraints.
|
|
// Note: Most importantly this guards against arbitrary wildcard matches
|
|
return false;
|
|
}
|
|
|
|
function isWildcardMatch(patternSegment: string, isLastSegment: boolean): boolean {
|
|
if (isWildcard(patternSegment)) {
|
|
return true;
|
|
}
|
|
return isLastSegment && patternSegment === WILDCARD_DOUBLE_ASTERISK;
|
|
}
|
|
let i = 0;
|
|
for (; i < patternSegments.length; i++) {
|
|
if (i >= fqnSegments.length) {
|
|
return false;
|
|
}
|
|
const fqnSegment = fqnSegments[i];
|
|
const patternSegment = patternSegments[i];
|
|
const isLastSegment = patternSegments.length === i + 1;
|
|
if (!isWildcardMatch(patternSegment, isLastSegment) && patternSegment !== fqnSegment.toLocaleLowerCase()) {
|
|
return false;
|
|
}
|
|
}
|
|
if (fqnSegments.length > i) {
|
|
// Special case: "**" as last segment matches arbitrary # of segments to the right
|
|
if (patternSegments[i - 1] === WILDCARD_DOUBLE_ASTERISK) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Matches the following patterns for "some/nested/repo", ordered by highest score:
|
|
// - exact: some/nested/repo ()
|
|
// - partial:
|
|
// - some/nested/*
|
|
// - some/*
|
|
// - generic:
|
|
// - */*/*
|
|
// - */*
|
|
// Does NOT match:
|
|
// - */repo (number of parts does not match)
|
|
// cmp. test cases in env-var-service.spec.ts
|
|
export function filter<T extends UserEnvVarValue>(vars: T[], owner: string, repo: string): T[] {
|
|
const matchedEnvVars = vars.filter((e) =>
|
|
matchEnvVarPattern(e.repositoryPattern, joinRepositoryPattern(owner, repo)),
|
|
);
|
|
const resmap = new Map<string, T[]>();
|
|
matchedEnvVars.forEach((e) => {
|
|
const l = resmap.get(e.name) || [];
|
|
l.push(e);
|
|
resmap.set(e.name, l);
|
|
});
|
|
|
|
// In cases of multiple matches per env var: find the best match
|
|
// Best match is the most specific pattern, where the left-most segment is most significant
|
|
function scoreMatchedEnvVar(e: T): number {
|
|
function valueSegment(segment: string): number {
|
|
switch (segment) {
|
|
case WILDCARD_ASTERISK:
|
|
return 2;
|
|
case WILDCARD_SHARP:
|
|
return 2;
|
|
case WILDCARD_DOUBLE_ASTERISK:
|
|
return 1;
|
|
default:
|
|
return 3;
|
|
}
|
|
}
|
|
const patternSegments = splitRepositoryPattern(e.repositoryPattern);
|
|
let result = 0;
|
|
for (const segment of patternSegments) {
|
|
// We can assume the pattern matches from context, so we just need to check whether it's a wildcard or not
|
|
const val = valueSegment(segment);
|
|
result = result * 10 + val;
|
|
}
|
|
return result;
|
|
}
|
|
const result = [];
|
|
for (const name of resmap.keys()) {
|
|
const candidates = resmap.get(name);
|
|
if (!candidates) {
|
|
// not sure how this can happen, but so be it
|
|
continue;
|
|
}
|
|
|
|
if (candidates.length == 1) {
|
|
result.push(candidates[0]);
|
|
continue;
|
|
}
|
|
|
|
let bestCandidate = candidates[0];
|
|
let bestScore = scoreMatchedEnvVar(bestCandidate);
|
|
for (const e of candidates.slice(1)) {
|
|
const score = scoreMatchedEnvVar(e);
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
bestCandidate = e;
|
|
}
|
|
}
|
|
result.push(bestCandidate!);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
export interface SSHPublicKeyValue {
|
|
name: string;
|
|
key: string;
|
|
}
|
|
export interface UserSSHPublicKey extends SSHPublicKeyValue {
|
|
id: string;
|
|
key: string;
|
|
userId: string;
|
|
fingerprint: string;
|
|
creationTime: string;
|
|
lastUsedTime?: string;
|
|
}
|
|
|
|
export type UserSSHPublicKeyValue = Omit<UserSSHPublicKey, "userId">;
|
|
|
|
export namespace SSHPublicKeyValue {
|
|
export function validate(value: SSHPublicKeyValue): string | undefined {
|
|
if (value.name.length === 0) {
|
|
return "Title must not be empty.";
|
|
}
|
|
if (value.name.length > 255) {
|
|
return "Title too long. Maximum value length is 255 characters.";
|
|
}
|
|
if (value.key.length === 0) {
|
|
return "Key must not be empty.";
|
|
}
|
|
try {
|
|
getData(value);
|
|
} catch (e) {
|
|
return "Key is invalid. You must supply a key in OpenSSH public key format.";
|
|
}
|
|
return;
|
|
}
|
|
|
|
export function getData(value: SSHPublicKeyValue) {
|
|
// 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'.
|
|
const regex =
|
|
/^(?<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>.*?))?$/;
|
|
const resultGroup = regex.exec(value.key.trim());
|
|
if (!resultGroup) {
|
|
throw new Error("Key is invalid.");
|
|
}
|
|
return {
|
|
type: resultGroup.groups?.["type"] as string,
|
|
key: resultGroup.groups?.["key"] as string,
|
|
email: resultGroup.groups?.["email"] || undefined,
|
|
};
|
|
}
|
|
|
|
export function getFingerprint(value: SSHPublicKeyValue) {
|
|
const data = getData(value);
|
|
const buf = Buffer.from(data.key, "base64");
|
|
// gitlab style
|
|
// const hash = createHash("md5").update(buf).digest("hex");
|
|
// github style
|
|
const hash = createHash("sha256").update(buf).digest("base64");
|
|
return hash;
|
|
}
|
|
|
|
export const MAXIMUM_KEY_LENGTH = 5;
|
|
}
|
|
|
|
export interface GitpodToken {
|
|
/** Hash value (SHA256) of the token (primary key). */
|
|
tokenHash: string;
|
|
|
|
/** Human readable name of the token */
|
|
name?: string;
|
|
|
|
/** Token kind */
|
|
type: GitpodTokenType;
|
|
|
|
/** The user the token belongs to. */
|
|
userId: string;
|
|
|
|
/** Scopes (e.g. limition to read-only) */
|
|
scopes: string[];
|
|
|
|
/** Created timestamp */
|
|
created: string;
|
|
}
|
|
|
|
export enum GitpodTokenType {
|
|
API_AUTH_TOKEN = 0,
|
|
MACHINE_AUTH_TOKEN = 1,
|
|
}
|
|
|
|
export interface OneTimeSecret {
|
|
id: string;
|
|
|
|
value: string;
|
|
|
|
expirationTime: string;
|
|
|
|
deleted: boolean;
|
|
}
|
|
|
|
export interface WorkspaceInstanceUser {
|
|
name?: string;
|
|
avatarUrl?: string;
|
|
instanceId: string;
|
|
userId: string;
|
|
lastSeen: string;
|
|
}
|
|
|
|
export interface Identity {
|
|
authProviderId: string;
|
|
authId: string;
|
|
authName: string;
|
|
primaryEmail?: string;
|
|
/** This is a flag that triggers the HARD DELETION of this entity */
|
|
deleted?: boolean;
|
|
// The last time this entry was touched during a signin. It's only set for SSO identities.
|
|
lastSigninTime?: string;
|
|
|
|
// @deprecated as no longer in use since '19
|
|
readonly?: boolean;
|
|
}
|
|
|
|
export type IdentityLookup = Pick<Identity, "authProviderId" | "authId">;
|
|
|
|
export namespace Identity {
|
|
export function is(data: any): data is Identity {
|
|
return (
|
|
data.hasOwnProperty("authProviderId") && data.hasOwnProperty("authId") && data.hasOwnProperty("authName")
|
|
);
|
|
}
|
|
export function equals(id1: IdentityLookup, id2: IdentityLookup) {
|
|
return id1.authProviderId === id2.authProviderId && id1.authId === id2.authId;
|
|
}
|
|
}
|
|
|
|
export interface Token {
|
|
value: string;
|
|
scopes: string[];
|
|
updateDate?: string;
|
|
expiryDate?: string;
|
|
reservedUntilDate?: string;
|
|
idToken?: string;
|
|
refreshToken?: string;
|
|
username?: string;
|
|
}
|
|
export interface TokenEntry {
|
|
uid: string;
|
|
authProviderId: string;
|
|
authId: string;
|
|
token: Token;
|
|
expiryDate?: string;
|
|
reservedUntilDate?: string;
|
|
refreshable?: boolean;
|
|
}
|
|
|
|
export interface EmailDomainFilterEntry {
|
|
domain: string;
|
|
negative: boolean;
|
|
}
|
|
|
|
export type AppInstallationPlatform = "github";
|
|
export type AppInstallationState = "claimed.user" | "claimed.platform" | "installed" | "uninstalled";
|
|
export interface AppInstallation {
|
|
platform: AppInstallationPlatform;
|
|
installationID: string;
|
|
ownerUserID?: string;
|
|
platformUserID?: string;
|
|
state: AppInstallationState;
|
|
creationTime: string;
|
|
lastUpdateTime: string;
|
|
}
|
|
|
|
export interface PendingGithubEvent {
|
|
id: string;
|
|
githubUserId: string;
|
|
creationDate: Date;
|
|
type: string;
|
|
event: string;
|
|
deleted: boolean;
|
|
}
|
|
|
|
export interface Snapshot {
|
|
id: string;
|
|
creationTime: string;
|
|
availableTime?: string;
|
|
originalWorkspaceId: string;
|
|
bucketId: string;
|
|
state: SnapshotState;
|
|
message?: string;
|
|
}
|
|
|
|
export type SnapshotState = "pending" | "available" | "error";
|
|
|
|
export interface Workspace {
|
|
id: string;
|
|
creationTime: string;
|
|
organizationId: string;
|
|
contextURL: string;
|
|
description: string;
|
|
ownerId: string;
|
|
projectId?: string;
|
|
context: WorkspaceContext;
|
|
config: WorkspaceConfig;
|
|
|
|
/**
|
|
* The source where to get the workspace base image from. This source is resolved
|
|
* during workspace creation. Once a base image has been built the information in here
|
|
* is superseded by baseImageNameResolved.
|
|
*/
|
|
imageSource?: WorkspaceImageSource;
|
|
|
|
/**
|
|
* The resolved, fix name of the workspace image. We only use this
|
|
* to access the logs during an image build.
|
|
*/
|
|
imageNameResolved?: string;
|
|
|
|
/**
|
|
* The resolved/built fixed named of the base image. This field is only set if the workspace
|
|
* already has its base image built.
|
|
*/
|
|
baseImageNameResolved?: string;
|
|
|
|
shareable?: boolean;
|
|
pinned?: boolean;
|
|
|
|
// workspace is hard-deleted on the database and about to be collected by periodic deleter
|
|
readonly deleted?: boolean;
|
|
|
|
/**
|
|
* Mark as deleted (user-facing). The actual deletion of the workspace content is executed
|
|
* with a (configurable) delay
|
|
*/
|
|
softDeleted?: WorkspaceSoftDeletion;
|
|
|
|
/**
|
|
* Marks the time when the workspace was marked as softDeleted. The actual deletion of the
|
|
* workspace content happens after a configurable period
|
|
*/
|
|
softDeletedTime?: string;
|
|
|
|
/**
|
|
* Marks the time when the workspace content has been deleted.
|
|
*/
|
|
contentDeletedTime?: string;
|
|
|
|
/**
|
|
* The time when the workspace is eligible for soft deletion. This is the time when the workspace
|
|
* is marked as softDeleted earliest.
|
|
*/
|
|
deletionEligibilityTime?: string;
|
|
|
|
type: WorkspaceType;
|
|
|
|
basedOnPrebuildId?: string;
|
|
|
|
basedOnSnapshotId?: string;
|
|
}
|
|
|
|
export type WorkspaceSoftDeletion = "user" | "gc";
|
|
|
|
export type WorkspaceType = "regular" | "prebuild" | "imagebuild";
|
|
|
|
export namespace Workspace {
|
|
export function getPullRequestNumber(ws: Workspace): number | undefined {
|
|
if (PullRequestContext.is(ws.context)) {
|
|
return ws.context.nr;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function getIssueNumber(ws: Workspace): number | undefined {
|
|
if (IssueContext.is(ws.context)) {
|
|
return ws.context.nr;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function getBranchName(ws: Workspace): string | undefined {
|
|
if (CommitContext.is(ws.context)) {
|
|
return ws.context.ref;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function getCommit(ws: Workspace): string | undefined {
|
|
if (CommitContext.is(ws.context)) {
|
|
return ws.context.revision && ws.context.revision.substr(0, 8);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
const NAME_PREFIX = "named:";
|
|
export function fromWorkspaceName(name?: Workspace["description"]): string | undefined {
|
|
if (name?.startsWith(NAME_PREFIX)) {
|
|
return name.slice(NAME_PREFIX.length);
|
|
}
|
|
return undefined;
|
|
}
|
|
export function toWorkspaceName(name?: Workspace["description"]): string {
|
|
if (!name || name?.trim().length === 0) {
|
|
return "no-name";
|
|
}
|
|
return `${NAME_PREFIX}${name}`;
|
|
}
|
|
}
|
|
|
|
export interface GuessGitTokenScopesParams {
|
|
host: string;
|
|
repoUrl: string;
|
|
gitCommand: string;
|
|
}
|
|
|
|
export interface GuessedGitTokenScopes {
|
|
message?: string;
|
|
scopes?: string[];
|
|
}
|
|
|
|
export interface VSCodeConfig {
|
|
extensions?: string[];
|
|
}
|
|
|
|
export interface JetBrainsConfig {
|
|
intellij?: JetBrainsProductConfig;
|
|
goland?: JetBrainsProductConfig;
|
|
pycharm?: JetBrainsProductConfig;
|
|
phpstorm?: JetBrainsProductConfig;
|
|
rubymine?: JetBrainsProductConfig;
|
|
webstorm?: JetBrainsProductConfig;
|
|
rider?: JetBrainsProductConfig;
|
|
clion?: JetBrainsProductConfig;
|
|
rustrover?: JetBrainsProductConfig;
|
|
}
|
|
export interface JetBrainsProductConfig {
|
|
prebuilds?: JetBrainsPrebuilds;
|
|
vmoptions?: string;
|
|
}
|
|
export interface JetBrainsPrebuilds {
|
|
version?: "stable" | "latest" | "both";
|
|
}
|
|
|
|
export interface RepositoryCloneInformation {
|
|
url: string;
|
|
checkoutLocation?: string;
|
|
}
|
|
|
|
export interface CoreDumpConfig {
|
|
enabled?: boolean;
|
|
softLimit?: number;
|
|
hardLimit?: number;
|
|
}
|
|
|
|
export interface WorkspaceConfig {
|
|
mainConfiguration?: string;
|
|
additionalRepositories?: RepositoryCloneInformation[];
|
|
image?: ImageConfig;
|
|
ports?: PortConfig[];
|
|
tasks?: TaskConfig[];
|
|
checkoutLocation?: string;
|
|
workspaceLocation?: string;
|
|
gitConfig?: { [config: string]: string };
|
|
github?: GithubAppConfig;
|
|
vscode?: VSCodeConfig;
|
|
jetbrains?: JetBrainsConfig;
|
|
coreDump?: CoreDumpConfig;
|
|
ideCredentials?: string;
|
|
env?: { [env: string]: any };
|
|
|
|
/** deprecated. Enabled by default **/
|
|
experimentalNetwork?: boolean;
|
|
|
|
/**
|
|
* Where the config object originates from.
|
|
*
|
|
* repo - from the repository
|
|
* derived - computed based on analyzing the repository
|
|
* additional-content - config comes from additional content, usually provided through the project's configuration
|
|
* default - our static catch-all default config
|
|
*/
|
|
_origin?: "repo" | "derived" | "additional-content" | "default";
|
|
|
|
/**
|
|
* Set of automatically infered feature flags. That's not something the user can set, but
|
|
* that is set by gitpod at workspace creation time.
|
|
*/
|
|
_featureFlags?: NamedWorkspaceFeatureFlag[];
|
|
}
|
|
|
|
export interface GithubAppConfig {
|
|
prebuilds?: GithubAppPrebuildConfig;
|
|
}
|
|
export interface GithubAppPrebuildConfig {
|
|
master?: boolean;
|
|
branches?: boolean;
|
|
pullRequests?: boolean;
|
|
pullRequestsFromForks?: boolean;
|
|
addCheck?: boolean | "prevent-merge-on-error";
|
|
addBadge?: boolean;
|
|
addLabel?: boolean | string;
|
|
addComment?: boolean;
|
|
}
|
|
export namespace GithubAppPrebuildConfig {
|
|
export function is(obj: boolean | GithubAppPrebuildConfig): obj is GithubAppPrebuildConfig {
|
|
return !(typeof obj === "boolean");
|
|
}
|
|
}
|
|
|
|
export type WorkspaceImageSource = WorkspaceImageSourceDocker | WorkspaceImageSourceReference;
|
|
export interface WorkspaceImageSourceDocker {
|
|
dockerFilePath: string;
|
|
dockerFileHash: string;
|
|
dockerFileSource?: Commit;
|
|
}
|
|
export namespace WorkspaceImageSourceDocker {
|
|
export function is(obj: object): obj is WorkspaceImageSourceDocker {
|
|
return "dockerFileHash" in obj && "dockerFilePath" in obj;
|
|
}
|
|
}
|
|
export interface WorkspaceImageSourceReference {
|
|
/** The resolved, fix base image reference */
|
|
baseImageResolved: string;
|
|
}
|
|
export namespace WorkspaceImageSourceReference {
|
|
export function is(obj: object): obj is WorkspaceImageSourceReference {
|
|
return "baseImageResolved" in obj;
|
|
}
|
|
}
|
|
|
|
export type PrebuiltWorkspaceState =
|
|
// the prebuild is queued and may start at anytime
|
|
| "queued"
|
|
// the workspace prebuild is currently running (i.e. there's a workspace pod deployed)
|
|
| "building"
|
|
// the prebuild was aborted
|
|
| "aborted"
|
|
// the prebuild timed out
|
|
| "timeout"
|
|
// the prebuild has finished (even if a headless task failed) and a snapshot is available
|
|
| "available"
|
|
// the prebuild (headless workspace) failed due to some system error
|
|
| "failed";
|
|
|
|
export interface PrebuiltWorkspace {
|
|
id: string;
|
|
cloneURL: string;
|
|
branch?: string;
|
|
projectId?: string;
|
|
commit: string;
|
|
buildWorkspaceId: string;
|
|
creationTime: string;
|
|
state: PrebuiltWorkspaceState;
|
|
statusVersion: number;
|
|
error?: string;
|
|
snapshot?: string;
|
|
}
|
|
|
|
export type PrebuiltWorkspaceWithWorkspace = PrebuiltWorkspace & { workspace: Workspace };
|
|
|
|
export namespace PrebuiltWorkspace {
|
|
export function isDone(pws: PrebuiltWorkspace) {
|
|
return (
|
|
pws.state === "available" || pws.state === "timeout" || pws.state === "aborted" || pws.state === "failed"
|
|
);
|
|
}
|
|
|
|
export function isAvailable(pws: PrebuiltWorkspace) {
|
|
return pws.state === "available" && !!pws.snapshot;
|
|
}
|
|
|
|
export function buildDidSucceed(pws: PrebuiltWorkspace) {
|
|
return pws.state === "available" && !pws.error;
|
|
}
|
|
}
|
|
|
|
export interface PrebuiltWorkspaceUpdatable {
|
|
id: string;
|
|
prebuiltWorkspaceId: string;
|
|
owner: string;
|
|
repo: string;
|
|
isResolved: boolean;
|
|
installationId: string;
|
|
/**
|
|
* the commitSHA of the commit that triggered the prebuild
|
|
*/
|
|
commitSHA?: string;
|
|
issue?: string;
|
|
contextUrl?: string;
|
|
}
|
|
|
|
export type PortOnOpen = "open-browser" | "open-preview" | "notify" | "ignore" | "ignore-completely";
|
|
|
|
export interface PortConfig {
|
|
port: number;
|
|
onOpen?: PortOnOpen;
|
|
visibility?: PortVisibility;
|
|
protocol?: PortProtocol;
|
|
description?: string;
|
|
name?: string;
|
|
}
|
|
export namespace PortConfig {
|
|
export function is(config: any): config is PortConfig {
|
|
return config && "port" in config && typeof config.port === "number";
|
|
}
|
|
}
|
|
|
|
export interface PortRangeConfig {
|
|
port: string;
|
|
onOpen?: PortOnOpen;
|
|
}
|
|
export namespace PortRangeConfig {
|
|
export function is(config: any): config is PortRangeConfig {
|
|
return config && "port" in config && (typeof config.port === "string" || config.port instanceof String);
|
|
}
|
|
}
|
|
|
|
export interface TaskConfig {
|
|
name?: string;
|
|
before?: string;
|
|
init?: string;
|
|
prebuild?: string;
|
|
command?: string;
|
|
env?: { [env: string]: any };
|
|
openIn?: "bottom" | "main" | "left" | "right";
|
|
openMode?: "split-top" | "split-left" | "split-right" | "split-bottom" | "tab-before" | "tab-after";
|
|
}
|
|
|
|
export namespace TaskConfig {
|
|
export function is(config: any): config is TaskConfig {
|
|
return config && ("command" in config || "init" in config || "before" in config);
|
|
}
|
|
}
|
|
|
|
export namespace WorkspaceImageBuild {
|
|
export type Phase = "BaseImage" | "GitpodLayer" | "Error" | "Done";
|
|
export interface StateInfo {
|
|
phase: Phase;
|
|
currentStep?: number;
|
|
maxSteps?: number;
|
|
}
|
|
export interface LogContent {
|
|
data: number[]; // encode with "Array.from(UInt8Array)"", decode with "new UInt8Array(data)"
|
|
}
|
|
export type LogCallback = (info: StateInfo, content: LogContent | undefined) => void;
|
|
export namespace LogLine {
|
|
export const DELIMITER = "\r\n";
|
|
export const DELIMITER_REGEX = /\r?\n/;
|
|
}
|
|
}
|
|
|
|
export type ImageConfig = ImageConfigString | ImageConfigFile;
|
|
export type ImageConfigString = string;
|
|
export namespace ImageConfigString {
|
|
export function is(config: ImageConfig | undefined): config is ImageConfigString {
|
|
return typeof config === "string";
|
|
}
|
|
}
|
|
export interface ImageConfigFile {
|
|
// Path to the Dockerfile relative to repository root
|
|
file: string;
|
|
// Path to the docker build context relative to repository root
|
|
context?: string;
|
|
}
|
|
export namespace ImageConfigFile {
|
|
export function is(config: ImageConfig | undefined): config is ImageConfigFile {
|
|
return typeof config === "object" && "file" in config;
|
|
}
|
|
}
|
|
export interface ExternalImageConfigFile extends ImageConfigFile {
|
|
externalSource: Commit;
|
|
}
|
|
export namespace ExternalImageConfigFile {
|
|
export function is(config: any | undefined): config is ExternalImageConfigFile {
|
|
return typeof config === "object" && "file" in config && "externalSource" in config;
|
|
}
|
|
}
|
|
|
|
export interface WorkspaceContext {
|
|
title: string;
|
|
ref?: string;
|
|
/** 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. */
|
|
normalizedContextURL?: string;
|
|
forceCreateNewWorkspace?: boolean;
|
|
forceImageBuild?: boolean;
|
|
// The context can have non-blocking warnings that should be displayed to the user.
|
|
warnings?: string[];
|
|
}
|
|
|
|
export namespace WorkspaceContext {
|
|
export function is(context: any): context is WorkspaceContext {
|
|
return context && "title" in context;
|
|
}
|
|
}
|
|
|
|
export interface WithSnapshot {
|
|
snapshotBucketId: string;
|
|
}
|
|
export namespace WithSnapshot {
|
|
export function is(context: any): context is WithSnapshot {
|
|
return context && "snapshotBucketId" in context;
|
|
}
|
|
}
|
|
|
|
export interface WithPrebuild extends WithSnapshot {
|
|
prebuildWorkspaceId: string;
|
|
wasPrebuilt: true;
|
|
}
|
|
export namespace WithPrebuild {
|
|
export function is(context: any): context is WithPrebuild {
|
|
return context && WithSnapshot.is(context) && "prebuildWorkspaceId" in context && "wasPrebuilt" in context;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* WithDefaultConfig contexts disable the download of the gitpod.yml from the repository
|
|
* and fall back to the built-in configuration.
|
|
*/
|
|
export interface WithDefaultConfig {
|
|
withDefaultConfig: true;
|
|
}
|
|
|
|
export namespace WithDefaultConfig {
|
|
export function is(context: any): context is WithDefaultConfig {
|
|
return context && "withDefaultConfig" in context && context.withDefaultConfig;
|
|
}
|
|
|
|
export function mark(ctx: WorkspaceContext): WorkspaceContext & WithDefaultConfig {
|
|
return {
|
|
...ctx,
|
|
withDefaultConfig: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
export interface SnapshotContext extends WorkspaceContext, WithSnapshot {
|
|
snapshotId: string;
|
|
}
|
|
|
|
export namespace SnapshotContext {
|
|
export function is(context: any): context is SnapshotContext {
|
|
return context && WithSnapshot.is(context) && "snapshotId" in context;
|
|
}
|
|
}
|
|
|
|
export interface WithCommitHistory {
|
|
commitHistory?: string[];
|
|
additionalRepositoryCommitHistories?: {
|
|
cloneUrl: string;
|
|
commitHistory: string[];
|
|
}[];
|
|
}
|
|
|
|
export interface StartPrebuildContext extends WorkspaceContext, WithCommitHistory {
|
|
actual: WorkspaceContext;
|
|
project: Project;
|
|
branch?: string;
|
|
}
|
|
|
|
export namespace StartPrebuildContext {
|
|
export function is(context: any): context is StartPrebuildContext {
|
|
return context && "actual" in context;
|
|
}
|
|
}
|
|
|
|
export interface PrebuiltWorkspaceContext extends WorkspaceContext {
|
|
originalContext: WorkspaceContext;
|
|
prebuiltWorkspace: PrebuiltWorkspace;
|
|
snapshotBucketId?: string;
|
|
}
|
|
|
|
export namespace PrebuiltWorkspaceContext {
|
|
export function is(context: any): context is PrebuiltWorkspaceContext {
|
|
return context && "originalContext" in context && "prebuiltWorkspace" in context;
|
|
}
|
|
}
|
|
|
|
export interface WithReferrerContext extends WorkspaceContext {
|
|
referrer: string;
|
|
referrerIde?: string;
|
|
}
|
|
|
|
export namespace WithReferrerContext {
|
|
export function is(context: any): context is WithReferrerContext {
|
|
return context && "referrer" in context;
|
|
}
|
|
}
|
|
|
|
export interface WithEnvvarsContext extends WorkspaceContext {
|
|
envvars: EnvVarWithValue[];
|
|
}
|
|
|
|
export namespace WithEnvvarsContext {
|
|
export function is(context: any): context is WithEnvvarsContext {
|
|
return context && "envvars" in context;
|
|
}
|
|
}
|
|
|
|
export type RefType = "branch" | "tag" | "revision";
|
|
export namespace RefType {
|
|
export const getRefType = (commit: Commit): RefType => {
|
|
if (!commit.ref) {
|
|
return "revision";
|
|
}
|
|
// This fallback is meant to handle the cases where (for historic reasons) ref is present but refType is missing
|
|
return commit.refType || "branch";
|
|
};
|
|
}
|
|
|
|
export interface Commit {
|
|
repository: Repository;
|
|
revision: string;
|
|
|
|
// Might contain either a branch or a tag (determined by refType)
|
|
ref?: string;
|
|
|
|
// refType is only set if ref is present (and not for old workspaces, before this feature was added)
|
|
refType?: RefType;
|
|
}
|
|
|
|
export interface AdditionalContentContext extends WorkspaceContext {
|
|
/**
|
|
* utf-8 encoded contents that will be copied on top of the workspace's filesystem
|
|
*/
|
|
additionalFiles: { [filePath: string]: string };
|
|
}
|
|
|
|
export namespace AdditionalContentContext {
|
|
export function is(ctx: any): ctx is AdditionalContentContext {
|
|
return "additionalFiles" in ctx;
|
|
}
|
|
|
|
export function hasDockerConfig(ctx: any, config: WorkspaceConfig): boolean {
|
|
return is(ctx) && ImageConfigFile.is(config.image) && !!ctx.additionalFiles[config.image.file];
|
|
}
|
|
}
|
|
|
|
export interface OpenPrebuildContext extends WorkspaceContext {
|
|
openPrebuildID: string;
|
|
}
|
|
|
|
export namespace OpenPrebuildContext {
|
|
export function is(ctx: any): ctx is OpenPrebuildContext {
|
|
return "openPrebuildID" in ctx;
|
|
}
|
|
}
|
|
|
|
export interface CommitContext extends WorkspaceContext, GitCheckoutInfo {
|
|
/** @deprecated Moved to .repository.cloneUrl, left here for backwards-compatibility for old workspace contextes in the DB */
|
|
cloneUrl?: string;
|
|
|
|
/**
|
|
* The clone and checkout information for additional repositories in case of multi-repo projects.
|
|
*/
|
|
additionalRepositoryCheckoutInfo?: GitCheckoutInfo[];
|
|
}
|
|
|
|
export namespace CommitContext {
|
|
/**
|
|
* Creates a hash for all the commits of the CommitContext and all sub-repo commit infos.
|
|
* The hash is max 255 chars long.
|
|
* @param commitContext
|
|
* @returns hash for commitcontext
|
|
*/
|
|
export function computeHash(commitContext: CommitContext): string {
|
|
// for single commits we use the revision to be backward compatible.
|
|
if (
|
|
!commitContext.additionalRepositoryCheckoutInfo ||
|
|
commitContext.additionalRepositoryCheckoutInfo.length === 0
|
|
) {
|
|
return commitContext.revision;
|
|
}
|
|
const hasher = createHash("sha256");
|
|
hasher.update(commitContext.revision);
|
|
for (const info of commitContext.additionalRepositoryCheckoutInfo) {
|
|
hasher.update(info.revision);
|
|
}
|
|
return hasher.digest("hex");
|
|
}
|
|
|
|
export function isDefaultBranch(commitContext: CommitContext): boolean {
|
|
return commitContext.ref === commitContext.repository.defaultBranch;
|
|
}
|
|
}
|
|
|
|
export interface GitCheckoutInfo extends Commit {
|
|
checkoutLocation?: string;
|
|
upstreamRemoteURI?: string;
|
|
localBranch?: string;
|
|
}
|
|
|
|
export namespace CommitContext {
|
|
export function is(commit: any): commit is CommitContext {
|
|
return WorkspaceContext.is(commit) && "repository" in commit && "revision" in commit;
|
|
}
|
|
}
|
|
|
|
export interface PullRequestContext extends CommitContext {
|
|
nr: number;
|
|
ref: string;
|
|
base: {
|
|
repository: Repository;
|
|
ref: string;
|
|
};
|
|
}
|
|
|
|
export namespace PullRequestContext {
|
|
export function is(ctx: any): ctx is PullRequestContext {
|
|
return CommitContext.is(ctx) && "nr" in ctx && "ref" in ctx && "base" in ctx;
|
|
}
|
|
}
|
|
|
|
export interface IssueContext extends CommitContext {
|
|
nr: number;
|
|
ref: string;
|
|
localBranch: string;
|
|
}
|
|
|
|
export namespace IssueContext {
|
|
export function is(ctx: any): ctx is IssueContext {
|
|
return CommitContext.is(ctx) && "nr" in ctx && "ref" in ctx && "localBranch" in ctx;
|
|
}
|
|
}
|
|
|
|
export interface NavigatorContext extends CommitContext {
|
|
path: string;
|
|
isFile: boolean;
|
|
}
|
|
|
|
export namespace NavigatorContext {
|
|
export function is(ctx: any): ctx is NavigatorContext {
|
|
return CommitContext.is(ctx) && "path" in ctx && "isFile" in ctx;
|
|
}
|
|
}
|
|
|
|
export interface Repository {
|
|
host: string;
|
|
owner: string;
|
|
name: string;
|
|
cloneUrl: string;
|
|
/* Optional kind to differentiate between repositories of orgs/groups/projects and personal repos. */
|
|
repoKind?: string;
|
|
description?: string;
|
|
avatarUrl?: string;
|
|
webUrl?: string;
|
|
defaultBranch?: string;
|
|
/** Optional for backwards compatibility */
|
|
private?: boolean;
|
|
fork?: {
|
|
// The direct parent of this fork
|
|
parent: Repository;
|
|
};
|
|
/**
|
|
* Optional date when the repository was last pushed to.
|
|
*/
|
|
pushedAt?: string;
|
|
/**
|
|
* Optional display name (e.g. for Bitbucket)
|
|
*/
|
|
displayName?: string;
|
|
}
|
|
|
|
export interface RepositoryInfo {
|
|
url: string;
|
|
name: string;
|
|
}
|
|
|
|
export interface Branch {
|
|
name: string;
|
|
commit: CommitInfo;
|
|
htmlUrl: string;
|
|
}
|
|
|
|
export interface CommitInfo {
|
|
author: string;
|
|
sha: string;
|
|
commitMessage: string;
|
|
authorAvatarUrl?: string;
|
|
authorDate?: string;
|
|
}
|
|
|
|
export interface WorkspaceInstancePortsChangedEvent {
|
|
type: "PortsChanged";
|
|
instanceID: string;
|
|
portsOpened: number[];
|
|
portsClosed: number[];
|
|
}
|
|
|
|
export namespace WorkspaceInstancePortsChangedEvent {
|
|
export function is(data: any): data is WorkspaceInstancePortsChangedEvent {
|
|
return data && data.type == "PortsChanged";
|
|
}
|
|
}
|
|
|
|
export interface WorkspaceSession {
|
|
workspace: Workspace;
|
|
instance: WorkspaceInstance;
|
|
metrics?: WorkspaceInstanceMetrics;
|
|
}
|
|
export interface WorkspaceInfo {
|
|
workspace: Workspace;
|
|
latestInstance?: WorkspaceInstance;
|
|
}
|
|
|
|
export namespace WorkspaceInfo {
|
|
export function lastActiveISODate(info: WorkspaceInfo): string {
|
|
return info.latestInstance?.creationTime || info.workspace.creationTime;
|
|
}
|
|
}
|
|
|
|
export type RunningWorkspaceInfo = WorkspaceInfo & { latestInstance: WorkspaceInstance };
|
|
|
|
export interface WorkspaceCreationResult {
|
|
createdWorkspaceId?: string;
|
|
workspaceURL?: string;
|
|
existingWorkspaces?: WorkspaceInfo[];
|
|
}
|
|
|
|
export namespace WorkspaceCreationResult {
|
|
export function is(data: any): data is WorkspaceCreationResult {
|
|
return (
|
|
data &&
|
|
("createdWorkspaceId" in data ||
|
|
"existingWorkspaces" in data ||
|
|
"runningWorkspacePrebuild" in data ||
|
|
"runningPrebuildWorkspaceID" in data)
|
|
);
|
|
}
|
|
}
|
|
|
|
export interface AuthProviderInfo {
|
|
readonly authProviderId: string;
|
|
readonly authProviderType: string;
|
|
readonly host: string;
|
|
readonly ownerId?: string;
|
|
readonly organizationId?: string;
|
|
readonly verified: boolean;
|
|
readonly hiddenOnDashboard?: boolean;
|
|
readonly disallowLogin?: boolean;
|
|
readonly icon?: string;
|
|
readonly description?: string;
|
|
|
|
readonly settingsUrl?: string;
|
|
readonly scopes?: string[];
|
|
readonly requirements?: {
|
|
readonly default: string[];
|
|
readonly publicRepo: string[];
|
|
readonly privateRepo: string[];
|
|
};
|
|
}
|
|
|
|
export interface AuthProviderEntry {
|
|
readonly id: string;
|
|
readonly type: AuthProviderEntry.Type;
|
|
readonly host: string;
|
|
readonly ownerId: string;
|
|
readonly organizationId?: string;
|
|
|
|
readonly status: AuthProviderEntry.Status;
|
|
|
|
readonly oauth: OAuth2Config;
|
|
/** A random string that is to change whenever oauth changes (enforced on DB level) */
|
|
readonly oauthRevision?: string;
|
|
}
|
|
|
|
export interface OAuth2Config {
|
|
readonly clientId: string;
|
|
readonly clientSecret: string;
|
|
readonly callBackUrl: string;
|
|
readonly authorizationUrl: string;
|
|
readonly tokenUrl: string;
|
|
readonly scope?: string;
|
|
readonly scopeSeparator?: string;
|
|
|
|
readonly settingsUrl?: string;
|
|
readonly authorizationParams?: { [key: string]: string };
|
|
}
|
|
|
|
export namespace AuthProviderEntry {
|
|
export type Type = "GitHub" | "GitLab" | "Bitbucket" | "BitbucketServer" | "AzureDevOps" | string;
|
|
export type Status = "pending" | "verified";
|
|
|
|
/**
|
|
* Some auth providers require additional configuration like Azure DevOps.
|
|
*/
|
|
export interface OAuth2CustomConfig {
|
|
/**
|
|
* The URL to the authorize endpoint of the provider.
|
|
*/
|
|
authorizationUrl?: string;
|
|
/**
|
|
* The URL to the oauth token endpoint of the provider.
|
|
*/
|
|
tokenUrl?: string;
|
|
}
|
|
export type NewEntry = Pick<AuthProviderEntry, "ownerId" | "host" | "type"> & {
|
|
clientId?: string;
|
|
clientSecret?: string;
|
|
} & OAuth2CustomConfig;
|
|
export type UpdateEntry = Pick<AuthProviderEntry, "id" | "ownerId"> & {
|
|
clientId?: string;
|
|
clientSecret?: string;
|
|
} & OAuth2CustomConfig;
|
|
export type NewOrgEntry = NewEntry & {
|
|
organizationId: string;
|
|
};
|
|
export type UpdateOrgEntry = Pick<AuthProviderEntry, "id"> & {
|
|
clientId?: string;
|
|
clientSecret?: string;
|
|
organizationId: string;
|
|
} & OAuth2CustomConfig;
|
|
export type UpdateOAuth2Config = Pick<OAuth2Config, "clientId" | "clientSecret"> & OAuth2CustomConfig;
|
|
export function redact(entry: AuthProviderEntry): AuthProviderEntry {
|
|
return {
|
|
...entry,
|
|
oauth: {
|
|
...entry.oauth,
|
|
clientSecret: "redacted",
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
export interface Configuration {
|
|
readonly isDedicatedInstallation: boolean;
|
|
}
|
|
|
|
export interface StripeConfig {
|
|
individualUsagePriceIds: { [currency: string]: string };
|
|
teamUsagePriceIds: { [currency: string]: string };
|
|
}
|
|
|
|
export interface LinkedInProfile {
|
|
id: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
profilePicture: string;
|
|
emailAddress: string;
|
|
}
|
|
|
|
export type SuggestedRepository = {
|
|
url: string;
|
|
projectId?: string;
|
|
projectName?: string;
|
|
repositoryName?: string;
|
|
};
|