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; + } +}