Skip to content
15 changes: 15 additions & 0 deletions components/image-builder-bob/cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package cmd

import (
"errors"
"os"
"strings"
"time"

"github.com/spf13/cobra"
Expand All @@ -32,6 +34,14 @@ var buildCmd = &cobra.Command{

cfg, err := builder.GetConfigFromEnv()
if err != nil {
if errors.Is(err, builder.DockerfilePathNotExists) {
dockerfilePath := strings.TrimPrefix(os.Getenv("BOB_DOCKERFILE_PATH"), "/workspace/")
err = os.WriteFile("/workspace/.gitpod/bob.log", []byte("could not find Dockerfile at \""+dockerfilePath+"\". Please double-check the value specified in image.file in .gitpod.yml"), 0644)
if err != nil {
log.WithError(err).Error("cannot write init message to /workspace/.gitpod/bob.log")
}
}

log.WithError(err).Fatal("cannot get config")
return
}
Expand All @@ -43,6 +53,11 @@ var buildCmd = &cobra.Command{
if err != nil {
log.WithError(err).Error("build failed")

err := os.WriteFile("/workspace/.gitpod/bob.log", []byte(err.Error()), 0644)
Copy link
Member

Choose a reason for hiding this comment

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

🧡

if err != nil {
log.WithError(err).Error("cannot write error to /workspace/.gitpod/bob.log")
}

// make sure we're running long enough to have our logs read
if dt := time.Since(t0); dt < 5*time.Second {
time.Sleep(10 * time.Second)
Expand Down
4 changes: 3 additions & 1 deletion components/image-builder-bob/pkg/builder/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type Config struct {
localCacheImport string
}

var DockerfilePathNotExists = xerrors.Errorf("BOB_DOCKERFILE_PATH does not exist or isn't a file")

// GetConfigFromEnv extracts configuration from environment variables
func GetConfigFromEnv() (*Config, error) {
cfg := &Config{
Expand Down Expand Up @@ -63,7 +65,7 @@ func GetConfigFromEnv() (*Config, error) {
return nil, xerrors.Errorf("BOB_DOCKERFILE_PATH must begin with /workspace")
}
if stat, err := os.Stat(cfg.Dockerfile); err != nil || stat.IsDir() {
return nil, xerrors.Errorf("BOB_DOCKERFILE_PATH does not exist or isn't a file")
return nil, DockerfilePathNotExists
}
}

Expand Down
13 changes: 11 additions & 2 deletions components/server/src/azure-devops/azure-file-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { injectable, inject } from "inversify";

import { FileProvider, MaybeContent } from "../repohost/file-provider";
import { FileProvider, MaybeContent, RevisionNotFoundError } from "../repohost/file-provider";
import { Commit, User, Repository } from "@gitpod/gitpod-protocol";
import { AzureDevOpsApi } from "./azure-api";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
Expand Down Expand Up @@ -34,6 +34,15 @@ export class AzureDevOpsFileProvider implements FileProvider {
): Promise<string> {
const [azOrgId, azProject] = getOrgAndProject(repository.owner);
const repoName = repository.name;
const notFoundError = new RevisionNotFoundError(
`File ${path} does not exist in repository ${repository.owner}/${repository.name}`,
);
const fileExists =
(await this.getFileContent({ repository, revision: revisionOrBranch }, user, path)) !== undefined;
if (!fileExists) {
throw notFoundError;
}

const results = await Promise.allSettled([
this.azureDevOpsApi.getCommits(user, azOrgId, azProject, repoName, {
filterCommit: {
Expand Down Expand Up @@ -71,7 +80,7 @@ export class AzureDevOpsFileProvider implements FileProvider {
}
}
// TODO(hw): [AZ] proper handle error
throw new Error(`File ${path} does not exist in repository ${repository.owner}/${repository.name}`);
throw notFoundError;
}

public async getFileContent(commit: Commit, user: User, path: string): Promise<MaybeContent> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { Commit, Repository, User } from "@gitpod/gitpod-protocol";
import { inject, injectable } from "inversify";
import { FileProvider, MaybeContent } from "../repohost/file-provider";
import { FileProvider, MaybeContent, RevisionNotFoundError } from "../repohost/file-provider";
import { BitbucketServerApi } from "./bitbucket-server-api";

@injectable()
Expand All @@ -24,20 +24,28 @@ export class BitbucketServerFileProvider implements FileProvider {
path: string,
): Promise<string> {
const { owner, name, repoKind } = repository;

if (!repoKind) {
throw new Error("Repo kind is missing.");
}
const notFoundError = new RevisionNotFoundError(
`File ${path} does not exist in repository ${repository.owner}/${repository.name}`,
);

const fileExists =
(await this.getFileContent({ repository, revision: revisionOrBranch }, user, path)) !== undefined;
if (!fileExists) {
throw notFoundError;
}

const result = await this.api.getCommits(user, {
const commits = await this.api.getCommits(user, {
owner,
repoKind,
repositorySlug: name,
query: { limit: 1, path, shaOrRevision: revisionOrBranch },
});
const lastCommit = result.values?.[0]?.id;
const lastCommit = commits.values?.[0]?.id;
if (!lastCommit) {
throw new Error(`File ${path} does not exist in repository ${repository.owner}/${repository.name}`);
throw notFoundError;
}

return lastCommit;
Expand Down
6 changes: 4 additions & 2 deletions components/server/src/bitbucket/bitbucket-file-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { Commit, Repository, User } from "@gitpod/gitpod-protocol";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
import { inject, injectable } from "inversify";
import { FileProvider, MaybeContent } from "../repohost/file-provider";
import { FileProvider, MaybeContent, RevisionNotFoundError } from "../repohost/file-provider";
import { BitbucketApiFactory } from "./bitbucket-api-factory";

@injectable()
Expand Down Expand Up @@ -48,7 +48,9 @@ export class BitbucketFileProvider implements FileProvider {
return lastCommit;
} catch (err) {
if (err.status && err.status === 404) {
throw new Error(`File ${path} does not exist in repository ${repository.owner}/${repository.name}`);
throw new RevisionNotFoundError(
`File ${path} does not exist in repository ${repository.owner}/${repository.name}`,
);
}

log.error({ userId: user.id }, err);
Expand Down
18 changes: 13 additions & 5 deletions components/server/src/github/file-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { injectable, inject } from "inversify";

import { FileProvider, MaybeContent } from "../repohost/file-provider";
import { FileProvider, MaybeContent, RevisionNotFoundError } from "../repohost/file-provider";
import { Commit, User, Repository } from "@gitpod/gitpod-protocol";
import { GitHubRestApi } from "./api";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
Expand All @@ -29,21 +29,29 @@ export class GithubFileProvider implements FileProvider {
user: User,
path: string,
): Promise<string> {
const notFoundError = new RevisionNotFoundError(
`File ${path} does not exist in repository ${repository.owner}/${repository.name}`,
);
const fileExists =
(await this.getFileContent({ repository, revision: revisionOrBranch }, user, path)) !== undefined;
if (!fileExists) {
throw notFoundError;
}

const commits = (
await this.githubApi.run(user, (gh) =>
gh.repos.listCommits({
owner: repository.owner,
repo: repository.name,
sha: revisionOrBranch,
// per_page: 1, // we need just the last one right?
per_page: 1, // we just need the last one
path,
}),
)
).data;

const lastCommit = commits && commits[0];
if (!lastCommit) {
throw new Error(`File ${path} does not exist in repository ${repository.owner}/${repository.name}`);
throw notFoundError;
}

return lastCommit.sha;
Expand Down Expand Up @@ -83,7 +91,7 @@ export class GithubFileProvider implements FileProvider {
}
return undefined;
} catch (err) {
log.debug("Failed to get Github file content", err, {
log.debug("Failed to get GitHub file content", err, {
request: params,
});
}
Expand Down
25 changes: 17 additions & 8 deletions components/server/src/gitlab/file-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { injectable, inject } from "inversify";

import { FileProvider, MaybeContent } from "../repohost/file-provider";
import { FileProvider, MaybeContent, RevisionNotFoundError } from "../repohost/file-provider";
import { Commit, User, Repository } from "@gitpod/gitpod-protocol";
import { GitLabApi, GitLab } from "./api";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
Expand All @@ -29,19 +29,28 @@ export class GitlabFileProvider implements FileProvider {
user: User,
path: string,
): Promise<string> {
const result = await this.gitlabApi.run<GitLab.Commit[]>(user, async (g) => {
return g.Commits.all(`${repository.owner}/${repository.name}`, { path, refName: revisionOrBranch });
});
const notFoundError = new RevisionNotFoundError(
`File ${path} does not exist in repository ${repository.owner}/${repository.name}`,
);

if (GitLab.ApiError.is(result)) {
throw result;
const fileExists =
(await this.getFileContent({ repository, revision: revisionOrBranch }, user, path)) !== undefined;
if (!fileExists) {
throw notFoundError;
}

const lastCommit = result[0];
const commitsResult = await this.gitlabApi.run<GitLab.Commit[]>(user, async (g) => {
return g.Commits.all(`${repository.owner}/${repository.name}`, { path, refName: revisionOrBranch });
});
if (GitLab.ApiError.is(commitsResult)) {
throw commitsResult;
}

const lastCommit = commitsResult[0];
if (!lastCommit) {
throw new Error(`File ${path} does not exist in repository ${repository.owner}/${repository.name}`);
throw notFoundError;
}

return lastCommit.id;
}

Expand Down
7 changes: 7 additions & 0 deletions components/server/src/repohost/file-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import { User, Repository, Commit } from "@gitpod/gitpod-protocol";

export type MaybeContent = string | undefined;

export class RevisionNotFoundError extends Error {
constructor(message: string) {
super(message);
this.name = "RevisionNotFoundError";
}
}

export const FileProvider = Symbol("FileProvider");
export interface FileProvider {
getGitpodFileContent(commit: Commit, user: User): Promise<MaybeContent>;
Expand Down
15 changes: 9 additions & 6 deletions components/server/src/workspace/config-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { Config } from "../config";
import { EntitlementService } from "../billing/entitlement-service";
import { TeamDB } from "@gitpod/gitpod-db/lib";
import { InvalidGitpodYMLError } from "@gitpod/public-api-common/lib/public-api-errors";
import { RevisionNotFoundError } from "../repohost";

const POD_PATH_WORKSPACE_BASE = "/workspace";

Expand Down Expand Up @@ -258,12 +259,14 @@ export class ConfigProvider {
throw new Error(`Cannot fetch workspace image source for host: ${host}`);
}
const repoHost = hostContext.services;
const lastDockerFileSha = await repoHost.fileProvider.getLastChangeRevision(
repository,
revisionOrTagOrBranch,
user,
dockerFilePath,
);
const lastDockerFileSha = await repoHost.fileProvider
.getLastChangeRevision(repository, revisionOrTagOrBranch, user, dockerFilePath)
.catch((e) => {
if (e instanceof RevisionNotFoundError) {
return "";
}
throw e;
});
return {
repository,
revision: lastDockerFileSha,
Expand Down
29 changes: 17 additions & 12 deletions components/server/src/workspace/image-source-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
AdditionalContentContext,
} from "@gitpod/gitpod-protocol";
import { createHash } from "crypto";
import { RevisionNotFoundError } from "../repohost";

@injectable()
export class ImageSourceProvider {
Expand All @@ -43,12 +44,14 @@ export class ImageSourceProvider {
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,
);
const lastDockerFileSha = await hostContext.services.fileProvider
.getLastChangeRevision(repository, imgcfg.externalSource.revision, user, imgcfg.file)
.catch((e) => {
if (e instanceof RevisionNotFoundError) {
return "";
}
throw e;
});
result = <WorkspaceImageSourceDocker>{
dockerFilePath: imgcfg.file,
dockerFileSource: imgcfg.externalSource,
Expand All @@ -72,12 +75,14 @@ export class ImageSourceProvider {
if (!hostContext || !hostContext.services) {
throw new Error(`Cannot fetch workspace image source for host: ${context.repository.host}`);
}
const lastDockerFileSha = await hostContext.services.fileProvider.getLastChangeRevision(
context.repository,
context.revision,
user,
imgcfg.file,
);
const lastDockerFileSha = await hostContext.services.fileProvider
.getLastChangeRevision(context.repository, context.revision, user, imgcfg.file)
.catch((e) => {
if (e instanceof RevisionNotFoundError) {
return "";
}
throw e;
});
result = <WorkspaceImageSourceDocker>{
dockerFilePath: imgcfg.file,
dockerFileSource: context,
Expand Down
7 changes: 6 additions & 1 deletion components/supervisor/pkg/supervisor/supervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1601,7 +1601,12 @@ func stopWhenTasksAreDone(wg *sync.WaitGroup, cfg *Config, shutdown chan Shutdow
if success.Failed() {
var msg []byte
if cfg.isImageBuild() {
msg = []byte("image build failed (" + string(success) + "). This is likely due to a misconfiguration in your Dockerfile. Debug this using `gp validate` (visit https://www.gitpod.io/docs/configure/workspaces#validate-your-gitpod-configuration) to learn more")
logFromFile, err := os.ReadFile("/workspace/.gitpod/bob.log")
if err != nil {
msg = []byte("err while reading bob.log" + err.Error())
} else {
msg = []byte("image build failed: " + string(logFromFile) + ". Debug this using `gp validate` (visit https://www.gitpod.io/docs/configure/workspaces#validate-your-gitpod-configuration) to learn more")
}
} else {
msg = []byte("headless task failed: " + string(success))
}
Expand Down
Loading