Skip to content
Draft
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
38 changes: 38 additions & 0 deletions .github/workflows/test-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ on:
- "test-repo/**"
- "trufflehog-merge-excludes/**"
- "trufflehog-filter-findings/**"
- "store-debug-symbols/**"
- "restore-debug-symbols/**"
- "delete-debug-symbols/**"
- "upload-debug-symbols-to-sentry/**"

jobs:
test-js-supply-chain:
Expand All @@ -34,3 +38,37 @@ jobs:
python-version: '3.x'
- run: pip install pytest pyyaml
- run: pytest trufflehog-filter-findings/tests/

test-store-debug-symbols:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bats-core/bats-action@2
- run: bats store-debug-symbols/tests/

test-restore-debug-symbols:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- uses: bats-core/bats-action@2
- run: bats restore-debug-symbols/tests/

test-delete-debug-symbols:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- uses: bats-core/bats-action@2
- run: bats delete-debug-symbols/tests/

test-upload-debug-symbols-to-sentry:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bats-core/bats-action@2
- run: bats upload-debug-symbols-to-sentry/tests/
5 changes: 5 additions & 0 deletions checkout-ssh/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ inputs:
git-submodules:
description: "Checkout the project with git submodules"
required: false
fetch-depth:
description: "Number of commits to fetch. 0 fetches the full history, which tools like sentry-cli need to see the commits of a release."
required: false
default: '1'
runs:
using: "composite"
steps:
- uses: actions/checkout@v3
with:
lfs: ${{ inputs.git-lfs }}
submodules: ${{ inputs.git-submodules }}
fetch-depth: ${{ inputs.fetch-depth }}
ssh-key: ${{ inputs.git-submodules != 'false' && inputs.ssh-private-key || '' }}
- uses: webfactory/ssh-agent@v0.6.0
with:
Expand Down
38 changes: 38 additions & 0 deletions delete-debug-symbols/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: 'Delete debug symbols'
description: 'Delete the debug symbols a build stored in QB Spaces, once Sentry has them'
inputs:
access-key:
description: 'Digital Ocean Access Key'
required: true
secret-key:
description: 'Digital Ocean Secret Key'
required: true
space-name:
description: 'Name of the DO Space the symbols were stored in'
required: false
default: 'quickbird-artifacts'
space-region:
description: 'Region of the DO Space'
required: false
default: 'fra1'
build-number:
description: 'Build number the symbols were stored for. Must be the same one the build jobs passed to store-debug-symbols. Falls back to the workflow run id.'
required: false
default: ''
platforms:
description: 'Space separated platform labels to delete. Platforms without stored symbols are skipped.'
required: false
default: 'ios android-apk android-aab'
runs:
using: "composite"
steps:
- name: Delete debug symbols from QB Spaces
shell: bash
env:
INPUT_ACCESS_KEY: ${{ inputs.access-key }}
INPUT_SECRET_KEY: ${{ inputs.secret-key }}
INPUT_SPACE_NAME: ${{ inputs.space-name }}
INPUT_SPACE_REGION: ${{ inputs.space-region }}
INPUT_BUILD_NUMBER: ${{ inputs.build-number }}
INPUT_PLATFORMS: ${{ inputs.platforms }}
run: bash "$GITHUB_ACTION_PATH/scripts/delete_debug_symbols.sh"
61 changes: 61 additions & 0 deletions delete-debug-symbols/scripts/delete_debug_symbols.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -euo pipefail

# ── input validation ──────────────────────────────────────────────────────────

build_number="${INPUT_BUILD_NUMBER:-$GITHUB_RUN_ID}"

if [[ ! "$build_number" =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "::error::Invalid build number '$build_number' (allowed: letters, digits, '.', '_', '-')"
exit 1
fi

if ! curl --help all 2>/dev/null | grep -q -- '--aws-sigv4'; then
echo "::error::curl on this runner cannot sign S3 requests (needs curl 7.75+), found: $(curl --version | head -1)"
exit 1
fi

# ── credentials ───────────────────────────────────────────────────────────────

# Passing the keys as --user would expose them in the process list, which
# matters on shared self-hosted runners. A 0600 config file does not.
credentials="${RUNNER_TEMP}/qb-spaces-curl.conf"
(umask 077 && printf 'user = "%s:%s"\n' "$INPUT_ACCESS_KEY" "$INPUT_SECRET_KEY" > "$credentials")
trap 'rm -f "$credentials"' EXIT

# ── delete ────────────────────────────────────────────────────────────────────

endpoint="${SPACES_ENDPOINT:-https://${INPUT_SPACE_NAME}.${INPUT_SPACE_REGION}.digitaloceanspaces.com}"
prefix="${GITHUB_REPOSITORY##*/}/debug-symbols/${build_number}"
deleted=0

echo "Deleting debug symbols under '$prefix/'"

for platform in $INPUT_PLATFORMS; do
archive="debug-symbols-${platform}.tar.gz"

curl_status=0
status="$(curl --silent --show-error --config "$credentials" \
--request DELETE \
--aws-sigv4 "aws:amz:${INPUT_SPACE_REGION}:s3" \
--output /dev/null --write-out '%{http_code}' \
"${endpoint}/${prefix}/${archive}")" || curl_status=$?

if [[ "$curl_status" -ne 0 ]]; then
echo "::warning::Could not reach ${endpoint} to delete '$archive' (curl exit $curl_status)"
continue
fi

# S3 deletes are idempotent, so a missing key answers 204 just like a hit.
case "$status" in
200|204|404)
deleted=$((deleted + 1))
echo "Deleted $archive"
;;
*)
echo "::warning::Deleting '$archive' answered HTTP $status - it will stay in the Space"
;;
esac
done

echo "Deleted $deleted of $(echo "$INPUT_PLATFORMS" | wc -w | tr -d ' ') key(s)"
90 changes: 90 additions & 0 deletions delete-debug-symbols/tests/delete_debug_symbols.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bats

load "setup.bash"

setup() {
start_space
}

teardown() {
stop_space
}

@test "deletes the archive of every platform" {
publish_archive "ios"
publish_archive "android-apk"
publish_archive "android-aab"
run_delete
[ "$status" -eq 0 ]
[ -z "$(remaining_keys)" ]
}

@test "deletes only the platforms it was given" {
publish_archive "ios"
publish_archive "android-aab"
INPUT_PLATFORMS="ios" \
run_delete
[ "$status" -eq 0 ]
[ "$(remaining_keys)" = "debug-symbols-android-aab.tar.gz" ]
}

@test "a platform that was never stored is not an error" {
publish_archive "ios"
run_delete
[ "$status" -eq 0 ]
[ -z "$(remaining_keys)" ]
}

@test "reports how many keys it removed" {
publish_archive "ios"
INPUT_PLATFORMS="ios android-aab" \
run_delete
[ "$status" -eq 0 ]
[[ "$output" == *"Deleted 2 of 2 key(s)"* ]]
}

@test "leaves the archives of other builds alone" {
publish_archive "ios"
mkdir -p "${SPACE_ROOT}/kaarlo-mobile/debug-symbols/1700000000"
echo "other" > "${SPACE_ROOT}/kaarlo-mobile/debug-symbols/1700000000/debug-symbols-ios.tar.gz"
run_delete
[ "$status" -eq 0 ]
[ -f "${SPACE_ROOT}/kaarlo-mobile/debug-symbols/1700000000/debug-symbols-ios.tar.gz" ]
}

@test "a rejected delete warns instead of failing the job" {
publish_archive "ios"
INPUT_PLATFORMS="forbidden" \
run_delete
[ "$status" -eq 0 ]
[[ "$output" == *"::warning::Deleting 'debug-symbols-forbidden.tar.gz' answered HTTP 403"* ]]
}

@test "an unreachable Space warns instead of failing the job" {
stop_space
SPACES_ENDPOINT="http://127.0.0.1:${SPACE_PORT}" \
run_delete
[ "$status" -eq 0 ]
[[ "$output" == *"::warning::Could not reach"* ]]
}

@test "an invalid build number is rejected before any request" {
INPUT_BUILD_NUMBER="../../etc" \
run_delete
[ "$status" -eq 1 ]
[[ "$output" == *"::error::Invalid build number"* ]]
}

@test "the build number falls back to the run id" {
INPUT_BUILD_NUMBER="" \
run_delete
[ "$status" -eq 0 ]
[[ "$output" == *"kaarlo-mobile/debug-symbols/16512345678/"* ]]
}

@test "the credentials file is removed when the script exits" {
publish_archive "ios"
run_delete
[ "$status" -eq 0 ]
[ ! -f "${BATS_TEST_TMPDIR}/temp/qb-spaces-curl.conf" ]
}
50 changes: 50 additions & 0 deletions delete-debug-symbols/tests/fake_space.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Minimal stand-in for a DO Space: serves GET and DELETE over a directory.

Usage: fake_space.py <root> <port>

A key whose name contains "forbidden" answers 403, so the error path can be
exercised. Deletes are idempotent, the way S3 behaves.
"""
import http.server
import os
import sys

ROOT = os.path.abspath(sys.argv[1])
PORT = int(sys.argv[2])


class Handler(http.server.BaseHTTPRequestHandler):
def _local_path(self):
return os.path.join(ROOT, self.path.lstrip("/").split("?")[0])

def _empty(self, status):
self.send_response(status)
self.send_header("Content-Length", "0")
self.end_headers()

def do_GET(self):
path = self._local_path()
if "forbidden" in self.path:
return self._empty(403)
if not os.path.isfile(path):
return self._empty(404)
with open(path, "rb") as handle:
body = handle.read()
self.send_response(200)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)

def do_DELETE(self):
if "forbidden" in self.path:
return self._empty(403)
path = self._local_path()
if os.path.isfile(path):
os.remove(path)
self._empty(204)

def log_message(self, *args):
pass


http.server.HTTPServer(("127.0.0.1", PORT), Handler).serve_forever()
56 changes: 56 additions & 0 deletions delete-debug-symbols/tests/setup.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
TESTS_DIR="$(cd "$(dirname "${BATS_TEST_FILENAME}")" && pwd)"
SCRIPT="${TESTS_DIR}/../scripts/delete_debug_symbols.sh"
FAKE_SPACE="${TESTS_DIR}/fake_space.py"

BUILD_NUMBER="1764500000"
KEY_PREFIX="kaarlo-mobile/debug-symbols/${BUILD_NUMBER}"

start_space() {
SPACE_ROOT="${BATS_TEST_TMPDIR}/space"
mkdir -p "${SPACE_ROOT}/${KEY_PREFIX}"
SPACE_PORT="$(python3 -c 'import socket; s = socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()')"
SPACES_ENDPOINT="http://127.0.0.1:${SPACE_PORT}"

python3 "$FAKE_SPACE" "$SPACE_ROOT" "$SPACE_PORT" >/dev/null 2>&1 &
SPACE_PID="$!"

local attempt=0
until curl --silent --output /dev/null "${SPACES_ENDPOINT}/"; do
attempt=$((attempt + 1))
[ "$attempt" -lt 50 ] || {
echo "the test HTTP server did not come up" >&2
return 1
}
sleep 0.1
done
}

stop_space() {
[ -n "${SPACE_PID:-}" ] && kill "$SPACE_PID" 2>/dev/null
return 0
}

publish_archive() {
local platform="$1"
echo "archive" > "${SPACE_ROOT}/${KEY_PREFIX}/debug-symbols-${platform}.tar.gz"
}

run_delete() {
mkdir -p "${BATS_TEST_TMPDIR}/temp"
run env \
RUNNER_TEMP="${BATS_TEST_TMPDIR}/temp" \
GITHUB_REPOSITORY="QuickBirdEng/kaarlo-mobile" \
GITHUB_RUN_ID="16512345678" \
SPACES_ENDPOINT="${SPACES_ENDPOINT:-}" \
INPUT_ACCESS_KEY="DO00ACCESSKEY" \
INPUT_SECRET_KEY="s3cr3t/key+with=chars" \
INPUT_SPACE_NAME="quickbird-artifacts" \
INPUT_SPACE_REGION="fra1" \
INPUT_BUILD_NUMBER="${INPUT_BUILD_NUMBER-$BUILD_NUMBER}" \
INPUT_PLATFORMS="${INPUT_PLATFORMS-ios android-apk android-aab}" \
bash "$SCRIPT"
}

remaining_keys() {
ls -1 "${SPACE_ROOT}/${KEY_PREFIX}" 2>/dev/null | sort
}
Loading