Skip to content
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
157 changes: 157 additions & 0 deletions .github/workflows/_build-cloud.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Internal reusable workflow for building a non-OSS ("cloud") Docker image and
# pushing it to Amazon ECR.
name: Build Cloud Image

on:
workflow_call:
inputs:
environment:
description: "GitHub Environment supplying the Sentry vars/secrets. Also scopes the OIDC subject used to assume the ECR push role."
required: true
type: string
git_ref:
description: "Git ref to checkout and build"
required: true
type: string
docker_tags:
description: "Docker tags configuration for docker/metadata-action"
required: true
type: string
aws_region:
description: "Region the ECR repository lives in"
required: false
type: string
default: us-west-1

jobs:
build:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
permissions:
contents: read
# Required to request the OIDC token that assumes the AWS role.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.git_ref }}
submodules: "true"
fetch-depth: 0
Comment thread
brendan-kellam marked this conversation as resolved.
# Nothing after checkout talks to the remote, so don't leave the token
# behind in the workspace's .git/config.
persist-credentials: false

- name: Resolve build commit SHA
id: commit
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Validate environment configuration
env:
ENVIRONMENT: ${{ inputs.environment }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
AWS_ECR_ROLE_ARN: ${{ vars.AWS_ECR_ROLE_ARN }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT: ${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT: ${{ vars.SENTRY_BACKEND_PROJECT }}
run: |
missing=0
for name in SENTRY_AUTH_TOKEN AWS_ECR_ROLE_ARN \
NEXT_PUBLIC_SENTRY_ENVIRONMENT \
NEXT_PUBLIC_SENTRY_WEBAPP_DSN \
NEXT_PUBLIC_SENTRY_BACKEND_DSN \
SENTRY_ORG SENTRY_WEBAPP_PROJECT SENTRY_BACKEND_PROJECT; do
if [ -z "${!name}" ]; then
echo "::error::${name} is not set on the '${ENVIRONMENT}' environment (or the repository)."
missing=1
else
echo "ok: ${name}"
fi
done
if [ "$missing" -ne 0 ]; then
echo "::error::Refusing to build: the image would ship without Sentry wiring."
exit 1
fi

- name: Check Prisma migrations
uses: ./.github/actions/check-prisma-migrations

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ECR_ROLE_ARN }}
aws-region: ${{ inputs.aws_region }}

- name: Login to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2

# Each environment publishes to its own registry (see CicdStack), so a
# staging build can never overwrite a prod tag.
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ steps.ecr.outputs.registry }}/sourcebot-${{ inputs.environment }}
tags: ${{ inputs.docker_tags }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image
Comment thread
brendan-kellam marked this conversation as resolved.
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# SENTRY_RELEASE is the commit SHA rather than SOURCEBOT_VERSION so that
# every prod build gets a distinct release (prod tracks `main`, where the
# version only moves on a tagged release). packages/backend/src/instrument.ts
# reports NEXT_PUBLIC_BUILD_COMMIT_SHA as its release to match; the webapp
# gets SENTRY_RELEASE injected into its bundle by withSentryConfig.
build-args: |
NEXT_PUBLIC_BUILD_COMMIT_SHA=${{ steps.commit.outputs.sha }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN=${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY=${{ vars.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY }}
NEXT_PUBLIC_LANGFUSE_BASE_URL=${{ vars.NEXT_PUBLIC_LANGFUSE_BASE_URL }}
SENTRY_ORG=${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT=${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT=${{ vars.SENTRY_BACKEND_PROJECT }}
SENTRY_RELEASE=${{ steps.commit.outputs.sha }}
# Passed as a secret, not a build-arg: build args are recorded in layer
# metadata that `mode=max` exports to the cache. @see: Dockerfile
secrets: |
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
# Cache scope is per-environment, and distinct from the OSS build's
# (which is keyed on platform alone). Sharing a scope would let a build
# that never sees SENTRY_AUTH_TOKEN restore layers from one that did.
cache-from: type=gha,scope=cloud-${{ inputs.environment }}-amd64
cache-to: type=gha,mode=max,scope=cloud-${{ inputs.environment }}-amd64

- name: Summarize
env:
ENVIRONMENT: ${{ inputs.environment }}
COMMIT_SHA: ${{ steps.commit.outputs.sha }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
{
echo "### Pushed to ECR"
echo
echo "| | |"
echo "|---|---|"
echo "| Environment | \`${ENVIRONMENT}\` |"
echo "| Commit | \`${COMMIT_SHA}\` |"
echo "| Sentry release | \`${COMMIT_SHA}\` |"
echo
echo '```'
echo "$TAGS"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
28 changes: 28 additions & 0 deletions .github/workflows/release-cloud-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Release Sourcebot (Cloud - Production)

permissions:
contents: read
id-token: write

on:
push:
branches: ["main"]
tags: ["v*.*.*"]
workflow_dispatch:

concurrency:
group: release-cloud-prod-${{ github.ref }}
cancel-in-progress: false
Comment thread
brendan-kellam marked this conversation as resolved.

jobs:
build:
uses: ./.github/workflows/_build-cloud.yml
with:
environment: prod
git_ref: ${{ github.ref }}
docker_tags: |
type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }}
type=sha,format=long,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern=v{{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
secrets: inherit
22 changes: 5 additions & 17 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,12 @@ ENV NEXT_PUBLIC_LANGFUSE_BASE_URL=$NEXT_PUBLIC_LANGFUSE_BASE_URL
ARG NEXT_PUBLIC_BUILD_COMMIT_SHA
ENV NEXT_PUBLIC_BUILD_COMMIT_SHA=$NEXT_PUBLIC_BUILD_COMMIT_SHA

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
# @see : next.config.mjs
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_WEBAPP_PROJECT
ENV SENTRY_WEBAPP_PROJECT=$SENTRY_WEBAPP_PROJECT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
# -----------

RUN apk add --no-cache libc6-compat
Expand All @@ -92,7 +85,9 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/web install

ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn workspace @sourcebot/web build

RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
yarn workspace @sourcebot/web build
Comment thread
cursor[bot] marked this conversation as resolved.
ENV SKIP_ENV_VALIDATION=0
# ------------------------------

Expand All @@ -101,16 +96,10 @@ FROM node-alpine AS backend-builder
ENV SKIP_ENV_VALIDATION=1
# -----------

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_BACKEND_PROJECT
ENV SENTRY_BACKEND_PROJECT=$SENTRY_BACKEND_PROJECT
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# -----------
Expand All @@ -129,11 +118,10 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/backend install
RUN yarn workspace @sourcebot/backend build

# Upload source maps to Sentry if we have the necessary build-time args.
RUN if [ -n "$SENTRY_SMUAT" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
if [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
apk add --no-cache curl; \
curl -sL https://sentry.io/get-cli/ | sh; \
sentry-cli login --auth-token $SENTRY_SMUAT; \
sentry-cli sourcemaps inject --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
sentry-cli sourcemaps upload --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
fi
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/instrument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ const logger = createLogger('instrument');
if (!!env.NEXT_PUBLIC_SENTRY_BACKEND_DSN && !!env.NEXT_PUBLIC_SENTRY_ENVIRONMENT) {
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
// Must match the release our source maps are uploaded under, which the
// Dockerfile sets from SENTRY_RELEASE (the build's commit SHA). Falls back
// to the version for builds that don't pass a commit SHA.
release: env.NEXT_PUBLIC_BUILD_COMMIT_SHA ?? SOURCEBOT_VERSION,
environment: env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
});
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/web/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ export default withSentryConfig(nextConfig, {
// For all available options, see:
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_WEBAPP_PROJECT,
authToken: process.env.SENTRY_SMUAT,
release: process.env.SENTRY_RELEASE,
authToken: process.env.SENTRY_AUTH_TOKEN,
release: { name: process.env.SENTRY_RELEASE },

// Only print logs for uploading source maps in CI
silent: !process.env.CI,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
//
// Must be named `instrumentation-client.ts`. Next.js loads this file itself, whereas
// `sentry.client.config.ts` is only picked up by @sentry/nextjs' webpack plugin,
// which never runs since `next build` defaults to Turbopack.

import * as Sentry from "@sentry/nextjs";

Expand All @@ -9,9 +13,15 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

Comment thread
cursor[bot] marked this conversation as resolved.
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
} else {
console.debug("[client] Sentry was not initialized");
}

// Instruments App Router client-side navigations as spans. Next.js only reads this
// export from `instrumentation-client.ts`. A no-op when Sentry is uninitialized.
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
4 changes: 2 additions & 2 deletions packages/web/src/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ export async function register() {
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('../sentry.server.config');
await import('./sentry.server.config');
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config');
await import('./sentry.edge.config');
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Loading