Skip to content

[prebuild] Support opening a specfic prebuild #13768

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions components/dashboard/src/projects/Prebuild.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,14 @@ export default function () {
) : prebuild?.status === "available" ? (
<a
className="my-auto"
href={gitpodHostUrl.withContext(`${prebuild?.info.changeUrl}`).toString()}
href={gitpodHostUrl
.withContext(`open-prebuild/${prebuild?.info.id}/${prebuild?.info.changeUrl}`)
.toString()}
>
<button>New Workspace ({prebuild?.info.branch})</button>
<button>New Workspace (with this prebuild)</button>
</a>
) : (
<button disabled={true}>New Workspace ({prebuild?.info.branch})</button>
<button disabled={true}>New Workspace (with this prebuild)</button>
)}
</PrebuildLogs>
</div>
Expand Down
10 changes: 10 additions & 0 deletions components/gitpod-protocol/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,16 @@ export namespace AdditionalContentContext {
}
}

export interface OpenPrebuildContext extends WorkspaceContext {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I'm not super-fond of adding a ContextParser for this I understand that currently, it's the only accessible way that make things like this work.
Also, removing it later should not be too hard. 🙂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we use the commit context?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See related discussion: #13706 (comment)

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;
Expand Down
19 changes: 15 additions & 4 deletions components/server/ee/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
TeamMemberRole,
WORKSPACE_TIMEOUT_DEFAULT_SHORT,
PrebuildEvent,
OpenPrebuildContext,
} from "@gitpod/gitpod-protocol";
import { ResponseError } from "vscode-jsonrpc";
import {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -994,7 +1005,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
const makeResult = (instanceID: string): WorkspaceCreationResult => {
return <WorkspaceCreationResult>{
runningWorkspacePrebuild: {
prebuildID: prebuiltWorkspace.id,
prebuildID: prebuiltWorkspace!.id,
workspaceID,
instanceID,
starting: "queued",
Expand Down
13 changes: 13 additions & 0 deletions components/server/ee/src/workspace/workspace-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// 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,
Expand Down
2 changes: 2 additions & 0 deletions components/server/src/container-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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<WorkspaceContext> {
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;
}
}