Skip to content

Revert "[server] remove definitely-gp" #18335

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
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
1 change: 1 addition & 0 deletions components/gitpod-protocol/go/gitpod-service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1702,6 +1702,7 @@ type WorkspaceConfig struct {
// Where the config object originates from.
//
// repo - from the repository
// definitely-gp - from github.com/gitpod-io/definitely-gp
// derived - computed based on analyzing the repository
// default - our static catch-all default config
Origin string `json:"_origin,omitempty"`
Expand Down
3 changes: 2 additions & 1 deletion components/gitpod-protocol/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -970,11 +970,12 @@ export interface WorkspaceConfig {
* Where the config object originates from.
*
* repo - from the repository
* definitly-gp - from github.com/gitpod-io/definitely-gp
* 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";
_origin?: "repo" | "definitely-gp" | "derived" | "additional-content" | "default";

/**
* Set of automatically infered feature flags. That's not something the user can set, but
Expand Down
175 changes: 151 additions & 24 deletions components/server/src/workspace/config-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,55 @@
* See License.AGPL.txt in the project root for license information.
*/

import * as crypto from "crypto";
import { inject, injectable } from "inversify";
import fetch from "node-fetch";
import * as path from "path";
import * as crypto from "crypto";

import { log, LogContext } from "@gitpod/gitpod-protocol/lib/util/logging";
import {
AdditionalContentContext,
Commit,
User,
WorkspaceConfig,
CommitContext,
Repository,
ImageConfigString,
ExternalImageConfigFile,
ImageConfigFile,
ImageConfigString,
Commit,
NamedWorkspaceFeatureFlag,
ProjectConfig,
Repository,
User,
AdditionalContentContext,
WithDefaultConfig,
WorkspaceConfig,
ProjectConfig,
} from "@gitpod/gitpod-protocol";
import { GitpodFileParser } from "@gitpod/gitpod-protocol/lib/gitpod-file-parser";
import { log, LogContext } from "@gitpod/gitpod-protocol/lib/util/logging";

import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
import { MaybeContent } from "../repohost/file-provider";
import { ConfigurationService } from "../config/configuration-service";
import { HostContextProvider } from "../auth/host-context-provider";
import { AuthorizationService } from "../user/authorization-service";
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
import { Config } from "../config";
import { ConfigurationService } from "../config/configuration-service";
import { EntitlementService } from "../billing/entitlement-service";
import { TeamDB } from "@gitpod/gitpod-db/lib";

const POD_PATH_WORKSPACE_BASE = "/workspace";

@injectable()
export class ConfigProvider {
constructor(
@inject(GitpodFileParser) private readonly gitpodParser: GitpodFileParser,
@inject(HostContextProvider) private readonly hostContextProvider: HostContextProvider,
@inject(Config) private readonly config: Config,
@inject(ConfigurationService) private readonly configurationService: ConfigurationService,
) {}
static readonly DEFINITELY_GP_REPO: Repository = {
host: "github.com",
owner: "gitpod-io",
name: "definitely-gp",
cloneUrl: "https://github.com/gitpod-io/definitely-gp",
};

@inject(GitpodFileParser) protected readonly gitpodParser: GitpodFileParser;
@inject(HostContextProvider) protected readonly hostContextProvider: HostContextProvider;
@inject(AuthorizationService) protected readonly authService: AuthorizationService;
@inject(Config) protected readonly config: Config;
@inject(ConfigurationService) protected readonly configurationService: ConfigurationService;
@inject(EntitlementService) protected readonly entitlementService: EntitlementService;
@inject(TeamDB) protected readonly teamDB: TeamDB;

public async fetchConfig(
ctx: TraceContext,
Expand Down Expand Up @@ -83,10 +96,15 @@ export class ConfigProvider {
config.image = this.config.workspaceDefaults.workspaceImage;
} else if (ImageConfigFile.is(config.image)) {
const dockerfilePath = [configBasePath, config.image.file].filter((s) => !!s).join("/");
const repo = commit.repository;
const rev = commit.revision;
let repo = commit.repository;
let rev = commit.revision;
const image = config.image!;

if (config._origin === "definitely-gp") {
repo = ConfigProvider.DEFINITELY_GP_REPO;
rev = "master";
image.file = dockerfilePath;
}
if (!(AdditionalContentContext.is(commit) && commit.additionalFiles[dockerfilePath])) {
config.image = <ExternalImageConfigFile>{
...image,
Expand Down Expand Up @@ -125,7 +143,7 @@ export class ConfigProvider {
}
}

private async fetchCustomConfig(
protected async fetchCustomConfig(
ctx: TraceContext,
user: User,
commit: CommitContext,
Expand All @@ -136,7 +154,7 @@ export class ConfigProvider {

try {
let customConfig: WorkspaceConfig | undefined;
const configBasePath = "";
let configBasePath = "";
if (AdditionalContentContext.is(commit) && commit.additionalFiles[".gitpod.yml"]) {
customConfigString = commit.additionalFiles[".gitpod.yml"];
const parseResult = this.gitpodParser.parse(customConfigString);
Expand Down Expand Up @@ -165,6 +183,21 @@ export class ConfigProvider {
customConfigString = await contextRepoConfig;
let origin: WorkspaceConfig["_origin"] = "repo";

if (!customConfigString) {
/* We haven't found a Gitpod configuration file in the context repo - check definitely-gp.
*
* In case we had found a config file here, we'd still be checking the definitely GP repo, just to save some time.
* While all those checks will be in vain, they should not leak memory either as they'll simply
* be resolved and garbage collected.
*/
const definitelyGpConfig = this.fetchExternalGitpodFileContent({ span }, commit.repository);
const { content, basePath } = await definitelyGpConfig;
customConfigString = content;
// We do not only care about the config itself but also where we got it from
configBasePath = basePath;
origin = "definitely-gp";
}

if (!customConfigString) {
const inferredConfig = this.configurationService.guessRepositoryConfiguration(
{ span },
Expand Down Expand Up @@ -215,7 +248,7 @@ export class ConfigProvider {
};
}

private async fetchWorkspaceImageSourceDocker(
protected async fetchWorkspaceImageSourceDocker(
ctx: TraceContext,
repository: Repository,
revisionOrTagOrBranch: string,
Expand Down Expand Up @@ -254,7 +287,101 @@ export class ConfigProvider {
}
}

private async validateConfig(config: WorkspaceConfig, user: User): Promise<void> {
protected async fillInDefaultLocations(
cfg: WorkspaceConfig | undefined,
inferredConfig: Promise<WorkspaceConfig | undefined>,
): Promise<void> {
if (!cfg) {
// there is no config - return
return;
}

if (!cfg.checkoutLocation) {
const inferredCfg = await inferredConfig;
if (inferredCfg) {
cfg.checkoutLocation = inferredCfg.checkoutLocation;
}
}
if (!cfg.workspaceLocation) {
const inferredCfg = await inferredConfig;
if (inferredCfg) {
cfg.workspaceLocation = inferredCfg.workspaceLocation;
}
}
}

protected async fetchExternalGitpodFileContent(
ctx: TraceContext,
repository: Repository,
): Promise<{ content: MaybeContent; basePath: string }> {
const span = TraceContext.startSpan("fetchExternalGitpodFileContent", ctx);
span.setTag("repo", `${repository.owner}/${repository.name}`);

if (this.config.definitelyGpDisabled) {
span.finish();
return {
content: undefined,
basePath: `${repository.name}`,
};
}

try {
const ownerConfigBasePath = `${repository.name}/${repository.owner}`;
const baseConfigBasePath = `${repository.name}`;

const possibleConfigs = [
[this.fetchDefinitelyGpContent({ span }, `${ownerConfigBasePath}/.gitpod.yml`), ownerConfigBasePath],
[this.fetchDefinitelyGpContent({ span }, `${ownerConfigBasePath}/.gitpod`), ownerConfigBasePath],
[this.fetchDefinitelyGpContent({ span }, `${baseConfigBasePath}/.gitpod.yml`), baseConfigBasePath],
[this.fetchDefinitelyGpContent({ span }, `${baseConfigBasePath}/.gitpod`), baseConfigBasePath],
];
for (const [configPromise, basePath] of possibleConfigs) {
const ownerConfig = await configPromise;
if (ownerConfig !== undefined) {
return {
content: ownerConfig,
basePath: basePath as string,
};
}
}
return {
content: undefined,
basePath: baseConfigBasePath,
};
} catch (e) {
TraceContext.setError({ span }, e);
throw e;
} finally {
span.finish();
}
}

protected async fetchDefinitelyGpContent(ctx: TraceContext, filePath: string) {
const span = TraceContext.startSpan("fetchDefinitelyGpContent", ctx);
span.setTag("filePath", filePath);

try {
const url = `https://raw.githubusercontent.com/gitpod-io/definitely-gp/master/${filePath}`;
const response = await fetch(url, {
timeout: 10000,
method: "GET",
});
let content;
if (response.ok) {
try {
content = await response.text();
} catch {}
}
return content;
} catch (e) {
TraceContext.setError({ span }, e);
throw e;
} finally {
span.finish();
}
}

protected async validateConfig(config: WorkspaceConfig, user: User): Promise<void> {
// Make sure the projectRoot does not leave POD_PATH_WORKSPACE_BASE as that's a common
// assumption throughout the code (e.g. ws-daemon)
const checkoutLocation = config.checkoutLocation;
Expand All @@ -280,7 +407,7 @@ export class ConfigProvider {
}
}

private leavesWorkspaceBase(normalizedPath: string) {
protected leavesWorkspaceBase(normalizedPath: string) {
const pathSegments = normalizedPath.split(path.sep);
return normalizedPath.includes("..") || pathSegments.slice(0, 2).join("/") != POD_PATH_WORKSPACE_BASE;
}
Expand Down
25 changes: 22 additions & 3 deletions components/server/src/workspace/image-source-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ import {
WorkspaceImageSourceReference,
WorkspaceImageSourceDocker,
ImageConfigFile,
ExternalImageConfigFile,
User,
AdditionalContentContext,
} from "@gitpod/gitpod-protocol";
import { createHash } from "crypto";

@injectable()
export class ImageSourceProvider {
constructor(@inject(HostContextProvider) private readonly hostContextProvider: HostContextProvider) {}
@inject(HostContextProvider) protected readonly hostContextProvider: HostContextProvider;

public async getImageSource(
ctx: TraceContext,
Expand All @@ -35,7 +36,25 @@ export class ImageSourceProvider {
let result: WorkspaceImageSource;

const imgcfg = config.image;
if (ImageConfigFile.is(imgcfg)) {
if (ExternalImageConfigFile.is(imgcfg)) {
// we're asked to pull the Dockerfile from a repo possibly different than the one we're opening a workspace for (e.g. definitely-gp).
const repository = imgcfg.externalSource.repository;
const hostContext = this.hostContextProvider.get(repository.host);
if (!hostContext || !hostContext.services) {
throw new Error(`Cannot fetch workspace image source for host: ${repository.host}`);
}
const lastDockerFileSha = await hostContext.services.fileProvider.getLastChangeRevision(
repository,
imgcfg.externalSource.revision,
user,
imgcfg.file,
);
result = <WorkspaceImageSourceDocker>{
dockerFilePath: imgcfg.file,
dockerFileSource: imgcfg.externalSource,
dockerFileHash: lastDockerFileSha,
};
} else if (ImageConfigFile.is(imgcfg)) {
// if a dockerfile sits in the additional content we use its contents sha
if (
AdditionalContentContext.is(context) &&
Expand Down Expand Up @@ -81,7 +100,7 @@ export class ImageSourceProvider {
}
}

private getContentSHA(contents: string): string {
protected getContentSHA(contents: string): string {
return createHash("sha256").update(contents).digest("hex");
}
}