Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMET
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN

# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
# causing regular expressions parsing errors when making a request. It's unclear
Expand Down
8 changes: 8 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
# Always infer NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="$STRIPE_PUBLISHABLE_KEY"

# Always infer NEXT_PUBLIC_SENTRY_ENVIRONMENT
export NEXT_PUBLIC_SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT"

# Always infer NEXT_PUBLIC_SENTRY_WEBAPP_DSN
export NEXT_PUBLIC_SENTRY_WEBAPP_DSN="$SENTRY_WEBAPP_DSN"

# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
# with their actual desired runtime value.
find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
Expand All @@ -131,6 +137,8 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_POSTHOG_PAPIK|${NEXT_PUBLIC_POSTHOG_PAPIK}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY|${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT|${NEXT_PUBLIC_SENTRY_ENVIRONMENT}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN|${NEXT_PUBLIC_SENTRY_WEBAPP_DSN}|g" "$file"
done
}

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
dist/
!.env
!.env
# Sentry Config File
.sentryclirc
10 changes: 7 additions & 3 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
"scripts": {
"dev:watch": "tsc-watch --preserveWatchOutput --onSuccess \"yarn dev --cacheDir ../../.sourcebot\"",
"dev": "export PATH=\"$PWD/../../bin:$PATH\" && export CTAGS_COMMAND=ctags && node ./dist/index.js",
"build": "tsc",
"test": "vitest --config ./vitest.config.ts"
"build": "tsc && yarn sentry:sourcemaps",
"test": "vitest --config ./vitest.config.ts",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org sourcebot --project backend ./dist && sentry-cli sourcemaps upload --org sourcebot --project backend ./dist"
},
"devDependencies": {
"@types/argparse": "^2.0.16",
Expand All @@ -23,6 +24,9 @@
"dependencies": {
"@gitbeaker/rest": "^40.5.1",
"@octokit/rest": "^21.0.2",
"@sentry/cli": "^2.42.2",
"@sentry/node": "^9.3.0",
"@sentry/profiling-node": "^9.3.0",
"@sourcebot/crypto": "^0.1.0",
"@sourcebot/db": "^0.1.0",
"@sourcebot/error": "^0.1.0",
Expand All @@ -44,4 +48,4 @@
"strip-json-comments": "^5.0.1",
"winston": "^3.15.0"
}
}
}
7 changes: 6 additions & 1 deletion packages/backend/src/connectionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Redis } from 'ioredis';
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
import { BackendError, BackendException } from "@sourcebot/error";
import { captureEvent } from "./posthog.js";
import * as Sentry from "@sentry/node";

interface IConnectionManager {
scheduleConnectionSync: (connection: Connection) => Promise<void>;
Expand Down Expand Up @@ -94,9 +95,11 @@ export class ConnectionManager implements IConnectionManager {
});

if (!connection) {
throw new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
const e = new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
message: `Connection ${job.data.connectionId} not found`,
});
Sentry.captureException(e);
throw e;
}

// Reset the syncStatusMetadata to an empty object at the start of the sync job
Expand Down Expand Up @@ -146,6 +149,8 @@ export class ConnectionManager implements IConnectionManager {
})();
} catch (err) {
this.logger.error(`Failed to compile repo data for connection ${job.data.connectionId}: ${err}`);
Sentry.captureException(err);

if (err instanceof BackendException) {
throw err;
} else {
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/connectionUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as Sentry from "@sentry/node";

type ValidResult<T> = {
type: 'valid';
data: T[];
Expand Down Expand Up @@ -39,6 +41,7 @@ export function processPromiseResults<T>(
export function throwIfAnyFailed<T>(results: PromiseSettledResult<T>[]) {
const failedResult = results.find(result => result.status === 'rejected');
if (failedResult) {
Sentry.captureException(failedResult.reason);
throw failedResult.reason;
}
}
8 changes: 7 additions & 1 deletion packages/backend/src/environment.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import dotenv from 'dotenv';
import * as Sentry from "@sentry/node";

export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => {
if (required && !env && !defaultValue) {
throw new Error(`Missing required environment variable: ${env}`);
const e = new Error(`Missing required environment variable: ${env}`);
Sentry.captureException(e);
throw e;
}

return env ?? defaultValue;
Expand Down Expand Up @@ -37,3 +40,6 @@ export const FALLBACK_GITEA_TOKEN = getEnv(process.env.FALLBACK_GITEA_TOKEN);

export const INDEX_CONCURRENCY_MULTIPLE = getEnv(process.env.INDEX_CONCURRENCY_MULTIPLE);
export const REDIS_URL = getEnv(process.env.REDIS_URL, 'redis://localhost:6379')!;

export const SENTRY_BACKEND_DSN = getEnv(process.env.SENTRY_BACKEND_DSN);
export const SENTRY_ENVIRONMENT = getEnv(process.env.SENTRY_ENVIRONMENT, 'unknown')!;
11 changes: 9 additions & 2 deletions packages/backend/src/gerrit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import micromatch from "micromatch";
import { measure, fetchWithRetry } from './utils.js';
import { BackendError } from '@sourcebot/error';
import { BackendException } from '@sourcebot/error';
import * as Sentry from "@sentry/node";

// https://gerrit-review.googlesource.com/Documentation/rest-api.html
interface GerritProjects {
Expand Down Expand Up @@ -40,6 +41,7 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
const fetchFn = () => fetchAllProjects(url);
return fetchWithRetry(fetchFn, `projects from ${url}`, logger);
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand All @@ -50,7 +52,9 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
});

if (!projects) {
throw new Error(`Failed to fetch projects from ${url}`);
const e = new Error(`Failed to fetch projects from ${url}`);
Sentry.captureException(e);
throw e;
}

// exclude "All-Projects" and "All-Users" projects
Expand Down Expand Up @@ -89,11 +93,14 @@ const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
response = await fetch(endpointWithParams);
if (!response.ok) {
console.log(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`);
throw new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
const e = new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
status: response.status,
});
Sentry.captureException(e);
throw e;
}
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand Down
12 changes: 11 additions & 1 deletion packages/backend/src/gitea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import micromatch from 'micromatch';
import { PrismaClient } from '@sourcebot/db';
import { FALLBACK_GITEA_TOKEN } from './environment.js';
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
import * as Sentry from "@sentry/node";

const logger = createLogger('Gitea');

export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
Expand Down Expand Up @@ -132,6 +134,8 @@ const getReposOwnedByUsers = async <T>(users: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`User ${user} not found or no access`);
return {
Expand Down Expand Up @@ -170,6 +174,8 @@ const getReposForOrgs = async <T>(orgs: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand Down Expand Up @@ -206,6 +212,8 @@ const getRepos = async <T>(repos: string[], api: Api<T>) => {
data: [response.data]
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand Down Expand Up @@ -234,7 +242,9 @@ const paginate = async <T>(request: (page: number) => Promise<HttpResponse<T[],

const totalCountString = result.headers.get('x-total-count');
if (!totalCountString) {
throw new Error("Header 'x-total-count' not found");
const e = new Error("Header 'x-total-count' not found");
Sentry.captureException(e);
throw e;
}
const totalCount = parseInt(totalCountString);

Expand Down
18 changes: 16 additions & 2 deletions packages/backend/src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITHUB_TOKEN } from "./environment.js";
import { BackendException, BackendError } from "@sourcebot/error";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitHub");

export type OctokitRepository = {
Expand Down Expand Up @@ -53,15 +55,21 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
try {
await octokit.rest.users.getAuthenticated();
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 401)) {
throw new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
const e = new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
secretKey,
});
Sentry.captureException(e);
throw e;
}

throw new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
const e = new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
message: `Failed to authenticate with GitHub`,
});
Sentry.captureException(e);
throw e;
}
}

Expand Down Expand Up @@ -239,6 +247,8 @@ const getReposOwnedByUsers = async (users: string[], isAuthenticated: boolean, o
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`User ${user} not found or no access`);
return {
Expand Down Expand Up @@ -282,6 +292,8 @@ const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSi
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand Down Expand Up @@ -327,6 +339,8 @@ const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSigna
};

} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/gitlab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITLAB_TOKEN } from "./environment.js";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitLab");
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";

Expand Down Expand Up @@ -47,6 +49,7 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
logger.debug(`Found ${_projects.length} projects in ${durationMs}ms.`);
allRepos = allRepos.concat(_projects);
} catch (e) {
Sentry.captureException(e);
logger.error(`Failed to fetch all projects visible in ${config.url}.`, e);
throw e;
}
Expand All @@ -72,6 +75,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`Group ${group} not found or no access`);
Expand Down Expand Up @@ -106,6 +111,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`User ${user} not found or no access`);
Expand Down Expand Up @@ -138,6 +145,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data: [data]
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;

if (status === 404) {
Expand Down
7 changes: 7 additions & 0 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import "./instrument.js";

import * as Sentry from "@sentry/node";
import { ArgumentParser } from "argparse";
import { existsSync } from 'fs';
import { mkdir } from 'fs/promises';
Expand All @@ -11,6 +14,8 @@ const parser = new ArgumentParser({
description: "Sourcebot backend tool",
});

Sentry.captureException(new Error("AAAAAAAAAAAAAAAAAAAAA"));

type Arguments = {
configPath: string;
cacheDir: string;
Expand Down Expand Up @@ -48,6 +53,8 @@ main(prisma, context)
})
.catch(async (e) => {
console.error(e);
Sentry.captureException(e);

await prisma.$disconnect();
process.exit(1);
})
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/instrument.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as Sentry from "@sentry/node";
import { SOURCEBOT_VERSION, SENTRY_BACKEND_DSN, SENTRY_ENVIRONMENT } from "./environment.js";

Sentry.init({
dsn: SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
environment: SENTRY_ENVIRONMENT,
});
8 changes: 6 additions & 2 deletions packages/backend/src/repoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { existsSync, readdirSync, promises } from 'fs';
import { indexGitRepository } from "./zoekt.js";
import os from 'os';
import { PromClient } from './promClient.js';

import * as Sentry from "@sentry/node";
interface IRepoManager {
blockingPollLoop: () => void;
dispose: () => void;
Expand Down Expand Up @@ -258,7 +258,9 @@ export class RepoManager implements IRepoManager {
});
if (!existingRepo) {
this.logger.error(`Repo ${repo.id} not found`);
throw new Error(`Repo ${repo.id} not found`);
const e = new Error(`Repo ${repo.id} not found`);
Sentry.captureException(e);
throw e;
}
const repoAlreadyInIndexingState = existingRepo.repoIndexingStatus === RepoIndexingStatus.INDEXING;

Expand Down Expand Up @@ -287,6 +289,8 @@ export class RepoManager implements IRepoManager {
stats = await this.syncGitRepository(repo, repoAlreadyInIndexingState);
break;
} catch (error) {
Sentry.captureException(error);

attempts++;
this.promClient.repoIndexingReattemptsTotal.inc();
if (attempts === maxAttempts) {
Expand Down
Loading