diff --git a/components/dashboard/src/projects/Prebuild.tsx b/components/dashboard/src/projects/Prebuild.tsx
index 60eff4c659581d..c36ffd7e58987d 100644
--- a/components/dashboard/src/projects/Prebuild.tsx
+++ b/components/dashboard/src/projects/Prebuild.tsx
@@ -181,12 +181,14 @@ export default function () {
) : prebuild?.status === "available" ? (
-
+
) : (
-
+
)}
diff --git a/components/gitpod-protocol/src/protocol.ts b/components/gitpod-protocol/src/protocol.ts
index ab0fa2d8f92207..3e97767421e660 100644
--- a/components/gitpod-protocol/src/protocol.ts
+++ b/components/gitpod-protocol/src/protocol.ts
@@ -1200,6 +1200,16 @@ export namespace AdditionalContentContext {
}
}
+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;
diff --git a/components/server/ee/src/workspace/gitpod-server-impl.ts b/components/server/ee/src/workspace/gitpod-server-impl.ts
index d05a4d18b2463a..201b55362b52f6 100644
--- a/components/server/ee/src/workspace/gitpod-server-impl.ts
+++ b/components/server/ee/src/workspace/gitpod-server-impl.ts
@@ -47,6 +47,7 @@ import {
TeamMemberRole,
WORKSPACE_TIMEOUT_DEFAULT_SHORT,
PrebuildEvent,
+ OpenPrebuildContext,
} from "@gitpod/gitpod-protocol";
import { ResponseError } from "vscode-jsonrpc";
import {
@@ -963,9 +964,19 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
const logCtx: LogContext = { userId: user.id };
const cloneUrl = context.repository.cloneUrl;
- const prebuiltWorkspace = await this.workspaceDb
- .trace(ctx)
- .findPrebuiltWorkspaceByCommit(cloneUrl, commitSHAs);
+ let prebuiltWorkspace: PrebuiltWorkspace | undefined;
+ if (OpenPrebuildContext.is(context)) {
+ prebuiltWorkspace = await this.workspaceDb.trace(ctx).findPrebuildByID(context.openPrebuildID);
+ if (prebuiltWorkspace?.cloneURL !== cloneUrl) {
+ // prevent users from opening arbitrary prebuilds this way - they must match the clone URL so that the resource guards are correct.
+ return;
+ }
+ } else {
+ prebuiltWorkspace = await this.workspaceDb
+ .trace(ctx)
+ .findPrebuiltWorkspaceByCommit(cloneUrl, commitSHAs);
+ }
+
const logPayload = { mode, cloneUrl, commit: commitSHAs, prebuiltWorkspace };
log.debug(logCtx, "Looking for prebuilt workspace: ", logPayload);
if (!prebuiltWorkspace) {
@@ -994,7 +1005,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
const makeResult = (instanceID: string): WorkspaceCreationResult => {
return {
runningWorkspacePrebuild: {
- prebuildID: prebuiltWorkspace.id,
+ prebuildID: prebuiltWorkspace!.id,
workspaceID,
instanceID,
starting: "queued",
diff --git a/components/server/ee/src/workspace/workspace-factory.ts b/components/server/ee/src/workspace/workspace-factory.ts
index 815685ae39438b..ca195c502ad5e6 100644
--- a/components/server/ee/src/workspace/workspace-factory.ts
+++ b/components/server/ee/src/workspace/workspace-factory.ts
@@ -21,6 +21,7 @@ import {
PrebuiltWorkspace,
WorkspaceConfig,
WorkspaceImageSource,
+ OpenPrebuildContext,
} from "@gitpod/gitpod-protocol";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
import { LicenseEvaluator } from "@gitpod/licensor/lib";
@@ -371,6 +372,18 @@ export class WorkspaceFactoryEE extends WorkspaceFactory {
config._featureFlags = (config._featureFlags || []).concat(["persistent_volume_claim"]);
}
+ if (OpenPrebuildContext.is(context.originalContext)) {
+ // Because of incremental prebuilds, createForContext will take over the original context.
+ // To ensure we get the right commit when forcing a prebuild, we force the context here.
+ context.originalContext = buildWorkspace.context;
+
+ if (CommitContext.is(context.originalContext)) {
+ // We force the checkout of the revision rather than the ref/branch.
+ // Otherwise we'd the correct prebuild with the "wrong" Git status.
+ delete context.originalContext.ref;
+ }
+ }
+
const id = await this.generateWorkspaceID(context);
const newWs: Workspace = {
id,
diff --git a/components/server/src/container-module.ts b/components/server/src/container-module.ts
index e30d2835b3a89a..2ca483c0b94568 100644
--- a/components/server/src/container-module.ts
+++ b/components/server/src/container-module.ts
@@ -112,6 +112,7 @@ import { LivenessController } from "./liveness/liveness-controller";
import { IDEServiceClient, IDEServiceDefinition } from "@gitpod/ide-service-api/lib/ide.pb";
import { prometheusClientMiddleware } from "@gitpod/gitpod-protocol/lib/util/nice-grpc";
import { UsageService } from "./user/usage-service";
+import { OpenPrebuildPrefixContextParser } from "./workspace/open-prebuild-prefix-context-parser";
export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
bind(Config).toConstantValue(ConfigFile.fromFile());
@@ -187,6 +188,7 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
bind(IPrefixContextParser).to(ReferrerPrefixParser).inSingletonScope();
bind(IPrefixContextParser).to(EnvvarPrefixParser).inSingletonScope();
bind(IPrefixContextParser).to(ImageBuildPrefixContextParser).inSingletonScope();
+ bind(IPrefixContextParser).to(OpenPrebuildPrefixContextParser).inSingletonScope();
bind(GitTokenScopeGuesser).toSelf().inSingletonScope();
bind(GitTokenValidator).toSelf().inSingletonScope();
diff --git a/components/server/src/workspace/open-prebuild-prefix-context-parser.ts b/components/server/src/workspace/open-prebuild-prefix-context-parser.ts
new file mode 100644
index 00000000000000..8ace7fc79153f5
--- /dev/null
+++ b/components/server/src/workspace/open-prebuild-prefix-context-parser.ts
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2021 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 { User, WorkspaceContext } from "@gitpod/gitpod-protocol";
+import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
+import { OpenPrebuildContext } from "@gitpod/gitpod-protocol/src/protocol";
+import { inject, injectable } from "inversify";
+import { Config } from "../config";
+import { IPrefixContextParser } from "./context-parser";
+
+@injectable()
+export class OpenPrebuildPrefixContextParser implements IPrefixContextParser {
+ @inject(Config) protected readonly config: Config;
+ static PREFIX = /^\/?open-prebuild\/([^\/]*)\//;
+
+ findPrefix(user: User, context: string): string | undefined {
+ const result = OpenPrebuildPrefixContextParser.PREFIX.exec(context);
+ if (!result) {
+ return undefined;
+ }
+ return result[0];
+ }
+
+ public async handle(user: User, prefix: string, context: WorkspaceContext): Promise {
+ const match = OpenPrebuildPrefixContextParser.PREFIX.exec(prefix);
+ if (!match) {
+ log.error("Could not parse prefix " + prefix);
+ return context;
+ }
+
+ (context as OpenPrebuildContext).openPrebuildID = match[1];
+ return context;
+ }
+}