diff --git a/.github/actions/setup-sentry-cli/action.yml b/.github/actions/setup-sentry-cli/action.yml new file mode 100644 index 00000000000..cea58ffee65 --- /dev/null +++ b/.github/actions/setup-sentry-cli/action.yml @@ -0,0 +1,36 @@ +name: setup-sentry-cli +description: Install Sentry-CLI + +inputs: + sentry-version: + description: Sentry-CLI version to install + required: true + + +runs: + using: composite + steps: + - name: (*Nix) Install Sentry-CLI@${{ inputs.sentry-version }} + shell: bash -eux -o pipefail {0} + if: runner.os != 'Windows' + run: | + curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION="${{ inputs.sentry-version }}" sh + + - name: (Windows) Install scoop + shell: powershell + if: runner.os == 'Windows' + run: | + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression + + Join-Path (Resolve-Path ~).Path "scoop\shims" >> $Env:GITHUB_PATH + + - name: (Windows) Install sentry-cli + shell: powershell + if: runner.os == 'Windows' + run: | + scoop install sentry-cli@${{ inputs.sentry-version }} + + - name: Check sentry-cli is installed + shell: bash + run: sentry-cli --version diff --git a/.github/workflows/package-client.yml b/.github/workflows/package-client.yml index 5c974db39cb..a45e1bf855f 100644 --- a/.github/workflows/package-client.yml +++ b/.github/workflows/package-client.yml @@ -50,6 +50,7 @@ env: node-version: 18.12.0 wasm-pack-version: 0.12.1 WINFSP_VERSION: 2.0.23075 + sentry-cli-version: 2.37.0 permissions: contents: read @@ -86,6 +87,14 @@ jobs: path: ${{ runner.temp }}/version.patch run-id: ${{ inputs.version_patch_run_id || github.run_id }} + - name: Load version config + id: version + shell: bash + run: | + cat version.patch/version.ini > "$GITHUB_OUTPUT" + cat "$GITHUB_OUTPUT" + working-directory: ${{ runner.temp }} + - name: Apply version.patch run: git apply --allow-empty ${{ runner.temp }}/version.patch/version.patch @@ -98,6 +107,9 @@ jobs: with: tool: syft@0.84.0, wasm-pack@${{ env.wasm-pack-version }} + # TODO: wasm-pack do provide debug info by default + # https://github.com/rustwasm/wasm-pack/issues/1351 + # https://docs.sentry.io/platforms/native/data-management/debug-files/file-formats/#wasm - name: Build web bindings run: npm run build:release working-directory: bindings/web @@ -105,7 +117,9 @@ jobs: - name: Build web app run: npm run web:release env: - PARSEC_APP_SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE: parsec@${{ steps.version.outputs.full }} + SENTRY_DIST: web working-directory: client - name: Generate SBOM @@ -165,22 +179,19 @@ jobs: sed -i 's/node package.js --mode prod --platform linux dir/& --nightly/' snap/snapcraft.yaml working-directory: client/electron - # We need to patch the vite.config.js because we cannot pass the secret to the snap build (either via build-args or env). - - name: Patch vite config for snap build - run: >- - sed -i - -e s'/if (process.env.PARSEC_APP_SENTRY_AUTH_TOKEN)/if (true)/' - -e s';authToken: process.env.PARSEC_APP_SENTRY_AUTH_TOKEN;authToken: "${{ secrets.SENTRY_AUTH_TOKEN }}";' - vite.config.ts - working-directory: client - - - name: Patch snapcraft file for sentry auth token and vite mode - run: >- - sed -i - -e s';SENTRY_AUTH_TOKEN: __TOKEN__;SENTRY_AUTH_TOKEN: "${{ secrets.SENTRY_AUTH_TOKEN }}";' - -e s'/VITE_MODE: development/VITE_MODE: ${{ steps.version.outputs.type }}/' - snap/snapcraft.yaml - working-directory: client/electron + - name: Create env file for snapcraft + run: + ( + echo "SENTRY_CLI_VERSION=${{ env.sentry-cli-version }}"; + echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}"; + echo "SENTRY_RELEASE=parsec@${{ steps.version.outputs.full }}"; + echo "SENTRY_DIST=snapcraft-linux"; + echo "SENTRY_ORG=scille"; + echo "SENTRY_CLIENT_PROJECT=parsec3-frontend"; + echo "SENTRY_LIBPARSEC_PROJECT=parsec3-libparsec"; + echo "VITE_MODE=${{ steps.version.outputs.type }}"; + ) | tee snapcraft.env + shell: bash - name: Build snap run: | @@ -254,6 +265,10 @@ jobs: node-version: ${{ env.node-version }} timeout-minutes: 2 + - uses: ./.github/actions/setup-sentry-cli + with: + sentry-version: ${{ env.sentry-cli-version }} + - name: Download version.patch artifact uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # pin v4.1.8 with: @@ -316,8 +331,23 @@ jobs: # MacOS is really slow when build rust timeout-minutes: 30 + - name: Upload debug info to sentry + shell: bash + run: | + sentry-cli debug-files check dist/libparsec/index.node + sentry-cli debug-files upload --include-sources dist/libparsec/index.node + working-directory: bindings/electron + env: + SENTRY_ORG: scille + SENTRY_PROJECT: parsec3-libparsec + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + - name: Build client for electron run: npm run native:build -- --mode ${{ steps.version.outputs.type }} + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_DIST: ${{ matrix.platform }} + SENTRY_RELEASE: parsec@${{ steps.version.outputs.full }} working-directory: client timeout-minutes: 5 @@ -336,15 +366,20 @@ jobs: ${{ (matrix.platform == 'linux' || inputs.nightly_build) && '--' || '' }} ${{ matrix.platform == 'linux' && 'appimage' || '' }} ${{ inputs.nightly_build && '--nightly' || '' }} - env: - PARSEC_APP_SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} working-directory: client/electron timeout-minutes: 5 - name: Upload client electron sourcemaps - run: npm run sentry:sourcemaps + run: | + npm exec sentry-cli -- sourcemaps inject --release $SENTRY_RELEASE ./build + npm exec sentry-cli -- sourcemaps upload --release $SENTRY_RELEASE --dist $SENTRY_DIST ./build + shell: bash env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_DIST: ${{ matrix.platform }} + SENTRY_RELEASE: parsec@${{ steps.version.outputs.full }} + SENTRY_ORG: scille + SENTRY_PROJECT: parsec3-frontend working-directory: client/electron timeout-minutes: 1 @@ -357,7 +392,7 @@ jobs: run: syft packages --config=.syft.yaml --output=spdx-json=client/electron/dist/Parsec-SBOM-Electron-${{ matrix.artifact_tag }}.spdx.json . - name: Debug dist folder - if: runner.debug || false + if: runner.debug run: ls client/electron/dist - name: Build info diff --git a/bindings/electron/src/lib.rs b/bindings/electron/src/lib.rs index d48ad8b81ef..a7bfa22d89f 100644 --- a/bindings/electron/src/lib.rs +++ b/bindings/electron/src/lib.rs @@ -19,10 +19,13 @@ static SENTRY_CLIENT_GUARD: OnceCell = OnceCell::new(); fn init_sentry() { SENTRY_CLIENT_GUARD.get_or_init(|| { + let version = std::option_env!("CARGO_PKG_VERSION").expect("Missing cargo version"); sentry::init(( SENTRY_DSN_LIBPARSEC, ClientOptions { - release: sentry::release_name!(), + release: Some(std::borrow::Cow::Owned(format!( + "parsec-electron@{version}" + ))), ..Default::default() }, )) diff --git a/client/electron/snap/snapcraft.yaml b/client/electron/snap/snapcraft.yaml index 46296f432d3..5f33ef1ee95 100644 --- a/client/electron/snap/snapcraft.yaml +++ b/client/electron/snap/snapcraft.yaml @@ -57,7 +57,7 @@ parts: build-attributes: - enable-patchelf override-build: | - set -x + set -eux -o pipefail # Set system alias for python update-alternatives --install /usr/local/bin/python python $(which python3) 100 @@ -76,6 +76,21 @@ parts: npm clean-install npm run build:release + set -a + source $SNAPCRAFT_PROJECT_DIR/snapcraft.env + set +a + + if [ "${SENTRY_AUTH_TOKEN:=}" != "__TOKEN__" ] && [ -n "${SENTRY_AUTH_TOKEN:=}" ]; then + # Install sentry-cli + curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION=$SENTRY_CLI_VERSION sh + + # Upload debug info + SENTRY_PROJECT=$SENTRY_LIBPARSEC_PROJECT sentry-cli debug-files check dist/libparsec/index.node + SENTRY_PROJECT=$SENTRY_LIBPARSEC_PROJECT sentry-cli debug-files upload --include-sources dist/libparsec/index.node + else + echo "SENTRY_AUTH_TOKEN is not set, skipping source maps upload." >&2 + fi + cp -va dist/libparsec/index.node "$CRAFT_PART_INSTALL/libparsec.node" cp -va dist/libparsec/index.d.ts "$CRAFT_PART_INSTALL/libparsec.d.ts" @@ -113,11 +128,13 @@ parts: build-attributes: - enable-patchelf build-environment: - - # Define a placeholder for the token, it will be replaced by the CI/CD pipeline. - SENTRY_AUTH_TOKEN: __TOKEN__ - VITE_MODE: development override-build: | - set -x + set -eux + + set -a + source $SNAPCRAFT_PROJECT_DIR/snapcraft.env + set +a # Debug software versions node --version @@ -144,8 +161,9 @@ parts: # Compile typescript npx tsc - if [ "${SENTRY_AUTH_TOKEN}" != "__TOKEN__" ]; then - npm run sentry:sourcemaps + if [ "${SENTRY_AUTH_TOKEN:=}" != "__TOKEN__" ] && [ -n "${SENTRY_AUTH_TOKEN:=}" ]; then + SENTRY_PROJECT=$SENTRY_CLIENT_PROJECT npm exec sentry-cli -- sourcemaps inject --release $SENTRY_RELEASE ./build + SENTRY_PROJECT=$SENTRY_CLIENT_PROJECT npm exec sentry-cli -- sourcemaps upload --release $SENTRY_RELEASE --dist $SENTRY_DIST ./build else echo "SENTRY_AUTH_TOKEN is not set, skipping source maps upload." >&2 fi diff --git a/client/vite.config.ts b/client/vite.config.ts index 74057664c54..8582d64afe1 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -31,16 +31,15 @@ if (process.env.PARSEC_APP_TEST_MODE || process.env.APP_TEST_MODE) { if (process.env.PLATFORM !== undefined) { console.log(`PLATFORM environ set to \`${process.env.PLATFORM}\``); - if (process.env.PLATFORM === 'web') { - platform = 'web'; - } else if (process.env.PLATFORM === 'native') { - platform = 'native'; + const VALID_PLATFORMS = ['web', 'native']; + if (VALID_PLATFORMS.includes(process.env.PLATFORM)) { + platform = process.env.PLATFORM; } else { - throw new Error('Invalid value for PLATFORM environ variable, accepted values: `web`/`native`'); + throw new Error(`Invalid value for PLATFORM environ variable, accepted values: ${VALID_PLATFORMS.join(', ')}`); } } else { // Ain't nobody got time to set environ variable ! - console.log('PLATFORM environ variable not set, defaulting to `web`'); + console.warn('PLATFORM environ variable not set, defaulting to `web`'); platform = 'web'; } @@ -50,15 +49,21 @@ if (platform === 'web') { plugins.push(wasmPack([{ path: '../bindings/web/', name: 'libparsec_bindings_web' }])); } -if (process.env.PARSEC_APP_SENTRY_AUTH_TOKEN) { +if (process.env.SENTRY_AUTH_TOKEN) { const sentryPlugin = sentryVitePlugin({ org: 'scille', project: 'parsec3-frontend', - authToken: process.env.PARSEC_APP_SENTRY_AUTH_TOKEN, + authToken: process.env.SENTRY_AUTH_TOKEN, + release: { + // The name of the release, e.g: parsec@3.0.0 + name: process.env.SENTRY_RELEASE, + // Distribution identifier for this release, e.g.: web, native + dist: process.env.SENTRY_DIST, + }, }); plugins.push(sentryPlugin); } else { - console.log('PARSEC_APP_SENTRY_AUTH_TOKEN is not set'); + console.warn('SENTRY_AUTH_TOKEN is not set'); } // 3) Finally configure Vite diff --git a/misc/version_updater.py b/misc/version_updater.py index 5c07096732f..d458192fd84 100644 --- a/misc/version_updater.py +++ b/misc/version_updater.py @@ -71,6 +71,7 @@ def _only_major_version(version: str) -> str: r"ghcr.io/scille/parsec-cloud/parsec-testbed-server:[^\s]+", "ghcr.io/scille/parsec-cloud/parsec-testbed-server:{version}", ) +SENTRY_CLI_GA_VERSION = ReplaceRegex("sentry-cli-version: [0-9.]+", "sentry-cli-version: {version}") @enum.unique @@ -87,6 +88,7 @@ class Tool(enum.Enum): WinFSP = "winfsp" Testbed = "testbed" PreCommit = "pre-commit" + SentryCLI = "sentry-cli" def post_update_hook(self, updated_files: set[Path]) -> set[Path]: updated: set[Path] = set() @@ -190,7 +192,12 @@ def get_tools_version() -> dict[Tool, str]: def get_tool_version(tool: Tool) -> str: - return get_tools_version()[tool] + try: + return get_tools_version()[tool] + except KeyError as ke: + raise ValueError( + f"Tool {tool.value} not found in the versions file {VERSIONS_FILE}" + ) from ke def set_tool_version(tool: Tool, version: str) -> None: @@ -250,6 +257,7 @@ def set_tool_version(tool: Tool, version: str) -> None: Tool.Node: [NODE_GA_VERSION], Tool.WasmPack: [WASM_PACK_GA_VERSION], Tool.WinFSP: [CI_WINFSP_VERSION], + Tool.SentryCLI: [SENTRY_CLI_GA_VERSION], }, ROOT_DIR / "bindings/electron/package.json": { Tool.License: [JSON_LICENSE_FIELD], @@ -386,18 +394,7 @@ def set_tool_version(tool: Tool, version: str) -> None: ] }, ROOT_DIR / "misc/versions.toml": { - Tool.Rust: [ReplaceRegex(r'rust = "[0-9.]+"', 'rust = "{version}"')], - Tool.Python: [ReplaceRegex(r'python = "[0-9.]+"', 'python = "{version}"')], - Tool.Poetry: [ReplaceRegex(r'poetry = "[0-9.]+"', 'poetry = "{version}"')], - Tool.Node: [ReplaceRegex(r'node = "[0-9.]+"', 'node = "{version}"')], - Tool.WasmPack: [ReplaceRegex(r'wasm-pack = "[0-9.]+"', 'wasm-pack = "{version}"')], - Tool.Nextest: [ReplaceRegex(r'nextest = "[0-9.]+"', 'nextest = "{version}"')], - Tool.Parsec: [ReplaceRegex(r'parsec = "[0-9.]+.*"', 'parsec = "{version}"')], - Tool.License: [ReplaceRegex(r'license = "[^\"]*"', 'license = "{version}"')], - Tool.PostgreSQL: [ReplaceRegex(r'postgres = "[0-9.]+"', 'postgres = "{version}"')], - Tool.WinFSP: [ReplaceRegex(r'winfsp = "[0-9.]+"', 'winfsp = "{version}"')], - Tool.Testbed: [ReplaceRegex(r'testbed = "[0-9.]+.*"', 'testbed = "{version}"')], - Tool.PreCommit: [ReplaceRegex(r'pre-commit = "[0-9.]+"', 'pre-commit = "{version}"')], + k: [ReplaceRegex(rf'{k.value} = ".*"', f'{k.value} = "{{version}}"')] for k in Tool }, ROOT_DIR / "server/packaging/server/in-docker-build.sh": { Tool.Poetry: [ diff --git a/misc/versions.toml b/misc/versions.toml index d3c29685a21..39ab86d1a94 100644 --- a/misc/versions.toml +++ b/misc/versions.toml @@ -10,3 +10,4 @@ postgres = "14.10" winfsp = "2.0.23075" testbed = "3.0.3-a.0.dev.20000.fa2c652" pre-commit = "3.7.1" +sentry-cli = "2.37.0" diff --git a/server/parsec/logging.py b/server/parsec/logging.py index 8616b40390e..d9e6492dec5 100644 --- a/server/parsec/logging.py +++ b/server/parsec/logging.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging +import platform import sys from datetime import datetime, timezone from typing import Any, Callable, MutableMapping, TextIO, Union, cast @@ -319,7 +320,8 @@ def enable_sentry_logging(dsn: str, environment: str) -> None: sentry_sdk.init( dsn=dsn, environment=environment, - release=__version__, + release="parsec@" + __version__, + dist=platform.system(), integrations=[ # Note that Sentry automatically enables some integrations, like: # AsyncPGIntegration (https://docs.sentry.io/platforms/python/integrations/asyncpg/)