name: Linting and Tests on: workflow_call: env: DJANGO_SETTINGS_MODULE: settings.ci_test SKIP_SLACK_SDK_WARNING: True DATABASE_HOST: localhost RABBITMQ_URI: amqp://rabbitmq:rabbitmq@localhost:5672 SLACK_CLIENT_OAUTH_ID: 1 jobs: lint-entire-project: name: "Lint entire project" runs-on: ubuntu-latest-16-cores steps: - name: Checkout project uses: actions/checkout@v4 - name: Setup Python uses: ./.github/actions/setup-python with: install-dependencies: "false" - name: Install frontend dependencies uses: ./.github/actions/install-frontend-dependencies - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd #v3.0.1 lint-test-and-build-frontend: name: "Lint, test, and build frontend" runs-on: ubuntu-latest-16-cores steps: - name: Checkout project uses: actions/checkout@v4 - name: Install frontend dependencies uses: ./.github/actions/install-frontend-dependencies - name: Build, lint and test frontend working-directory: grafana-plugin run: pnpm lint && pnpm type-check && pnpm test && pnpm build test-technical-documentation: name: "Test technical documentation" runs-on: ubuntu-latest-16-cores steps: - name: "Check out code" uses: "actions/checkout@v4" - name: "Build website" # -e HUGO_REFLINKSERRORLEVEL=ERROR prevents merging broken refs with the downside # that no refs to external content can be used as these refs will not resolve in the # docs-base image. run: > docker run -v ${PWD}/docs/sources:/hugo/content/docs/oncall/latest -e HUGO_REFLINKSERRORLEVEL=ERROR --rm grafana/docs-base:latest /bin/bash -c 'echo -e "---\\nredirectURL: /hugo/content/docs/oncall/latest/\\ntype: redirect\\nversioned: true\\n---\\n" > /hugo/content/docs/oncall/_index.md; make hugo' lint-migrations-backend-mysql-rabbitmq: name: "Lint database migrations" runs-on: ubuntu-latest-16-cores services: rabbit_test: image: rabbitmq:3.12.0 env: RABBITMQ_DEFAULT_USER: rabbitmq RABBITMQ_DEFAULT_PASS: rabbitmq ports: - 5672:5672 mysql_test: image: mysql:8.0.32 env: MYSQL_DATABASE: oncall_local_dev MYSQL_ROOT_PASSWORD: local_dev_pwd ports: - 3306:3306 steps: - name: Checkout project uses: actions/checkout@v4 - name: Setup Python uses: ./.github/actions/setup-python - name: Lint migrations working-directory: engine # makemigrations --check = Exit with a non-zero status if model changes are missing migrations # and don't actually write them. run: | python manage.py makemigrations --check python manage.py lintmigrations # the following CI check is to prevent developers from dropping columns in a way that could cause downtime # (the proper way to drop columns is documented in dev/README.md) # # we've been bitten by this before (see https://raintank-corp.slack.com/archives/C081TNWM73N as an example) ensure-database-migrations-drop-columns-the-correct-way: name: "Ensure database migrations drop columns the correct way" runs-on: ubuntu-latest steps: - name: Checkout PR code uses: actions/checkout@v3 with: # Fetch all history so we can compare with the base branch fetch-depth: 0 # Checkout the head commit of the PR ref: ${{ github.event.pull_request.head.sha }} - name: Extract and validate base ref id: extract_base_ref shell: bash env: EVENT_NAME: ${{ github.event_name }} PR_BASE_REF: ${{ github.event.pull_request.base.ref }} RELEASE_TARGET: ${{ github.event.release.target_commitish }} run: | # Determine base ref from safe, pre-evaluated env variables if [[ "$EVENT_NAME" == "pull_request" ]]; then BASE_REF="$PR_BASE_REF" elif [[ "$EVENT_NAME" == "release" ]]; then BASE_REF="$RELEASE_TARGET" else echo "Unsupported event: $EVENT_NAME" exit 1 fi # Validate against safe pattern (alphanumeric, underscore, dash, dot, and forward slash only) if [[ ! "$BASE_REF" =~ ^[a-zA-Z0-9_/.-]+$ ]]; then echo "Invalid branch name pattern detected: $BASE_REF" exit 1 fi # Store validated ref for later steps echo "base_ref=$BASE_REF" >> $GITHUB_OUTPUT - name: Fetch base branch shell: bash run: | # Use validated ref SAFE_REF="${{ steps.extract_base_ref.outputs.base_ref }}" git fetch origin "${SAFE_REF}:refs/remotes/origin/${SAFE_REF}" - name: Check for RemoveField in Migrations # yamllint disable rule:line-length shell: bash run: | # Use validated ref SAFE_REF="${{ steps.extract_base_ref.outputs.base_ref }}" HEAD_SHA="${{ github.event.pull_request.head.sha }}" # Get the list of files changed in the PR using validated refs git diff --name-only "refs/remotes/origin/${SAFE_REF}...${HEAD_SHA}" > changed_files.txt # Filter for migration files grep -E '^.*/migrations/.*\.py$' changed_files.txt > migration_files.txt || true # Initialize a flag FAILED=0 # Check each migration file for 'migrations.RemoveField' if [ -s migration_files.txt ]; then while IFS= read -r file; do echo "Checking $file for migrations.RemoveField..." if grep -q 'migrations.RemoveField' "$file"; then echo "❌ Error: Found migrations.RemoveField in $file" FAILED=1 else echo "✅ No RemoveField found in $file" fi done < migration_files.txt else echo "No migration files changed." fi # Fail the job if RemoveField was found if [ "$FAILED" -eq 1 ]; then echo "❌ Error: Found migrations.RemoveField in one or more migration files. Please check out our documentation at https://github.com/grafana/oncall/tree/dev/dev#removing-a-nullable-field-from-a-model on how to properly drop columns." exit 1 fi # yamllint enable rule:line-length unit-test-helm-chart: name: "Helm Chart Unit Tests" runs-on: ubuntu-latest-16-cores steps: - name: Checkout project uses: actions/checkout@v4 - uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 #v4.2.0 with: version: v3.8.0 - name: Install helm unittest plugin run: helm plugin install https://github.com/helm-unittest/helm-unittest.git --version=v0.3.3 - name: Run tests run: helm unittest ./helm/oncall unit-test-backend-plugin: name: "Backend Tests: Plugin" runs-on: ubuntu-latest-16-cores steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: go-version: "1.21.5" - run: cd grafana-plugin && go test ./pkg/... unit-test-backend-mysql-rabbitmq: name: "Backend Tests: MySQL + RabbitMQ (RBAC enabled: ${{ matrix.rbac_enabled }})" runs-on: ubuntu-latest-16-cores strategy: matrix: rbac_enabled: ["True", "False"] env: ONCALL_TESTING_RBAC_ENABLED: ${{ matrix.rbac_enabled }} services: rabbit_test: image: rabbitmq:3.12.0 env: RABBITMQ_DEFAULT_USER: rabbitmq RABBITMQ_DEFAULT_PASS: rabbitmq ports: - 5672:5672 mysql_test: image: mysql:8.0.32 env: MYSQL_DATABASE: oncall_local_dev MYSQL_ROOT_PASSWORD: local_dev_pwd ports: - 3306:3306 steps: - name: Checkout project uses: actions/checkout@v4 - name: Setup Python uses: ./.github/actions/setup-python - name: Wait for MySQL to be ready working-directory: engine run: ./wait_for_test_mysql_start.sh - name: Test Django migrations work from blank slate working-directory: engine run: python manage.py migrate - name: Unit Test Backend working-directory: engine run: pytest -x unit-test-backend-postgresql-rabbitmq: name: "Backend Tests: PostgreSQL + RabbitMQ (RBAC enabled: ${{ matrix.rbac_enabled }})" runs-on: ubuntu-latest-16-cores strategy: matrix: rbac_enabled: ["True", "False"] env: DATABASE_TYPE: postgresql ONCALL_TESTING_RBAC_ENABLED: ${{ matrix.rbac_enabled }} services: rabbit_test: image: rabbitmq:3.12.0 env: RABBITMQ_DEFAULT_USER: rabbitmq RABBITMQ_DEFAULT_PASS: rabbitmq ports: - 5672:5672 postgresql_test: image: postgres:14.4 env: POSTGRES_DB: oncall_local_dev POSTGRES_PASSWORD: local_dev_pwd ports: - 5432:5432 # Set health checks to wait until postgres has started options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout project uses: actions/checkout@v4 - name: Setup Python uses: ./.github/actions/setup-python - name: Test Django migrations work from blank slate working-directory: engine run: python manage.py migrate - name: Unit Test Backend working-directory: engine run: pytest -x unit-test-backend-sqlite-redis: name: "Backend Tests: SQLite + Redis (RBAC enabled: ${{ matrix.rbac_enabled }})" runs-on: ubuntu-latest-16-cores strategy: matrix: rbac_enabled: ["True", "False"] env: DATABASE_TYPE: sqlite3 BROKER_TYPE: redis REDIS_URI: redis://localhost:6379 ONCALL_TESTING_RBAC_ENABLED: ${{ matrix.rbac_enabled }} services: redis_test: image: redis:7.0.15 ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout project uses: actions/checkout@v4 - name: Setup Python uses: ./.github/actions/setup-python - name: Test Django migrations work from blank slate working-directory: engine run: python manage.py migrate - name: Unit Test Backend working-directory: engine run: pytest -x unit-test-migrators: name: "Unit tests - Migrators" runs-on: ubuntu-latest-16-cores steps: - name: Checkout project uses: actions/checkout@v4 - name: Setup Python uses: ./.github/actions/setup-python with: python-requirements-paths: tools/migrators/requirements.txt - name: Unit Test Migrators working-directory: tools/migrators run: pytest -x mypy: name: "mypy" runs-on: ubuntu-latest-16-cores steps: - name: Checkout project uses: actions/checkout@v4 - name: Setup Python uses: ./.github/actions/setup-python - name: mypy Static Type Checking working-directory: engine run: mypy . end-to-end-tests: name: Standard e2e tests uses: ./.github/workflows/e2e-tests.yml strategy: matrix: grafana_version: - 10.3.0 - 11.2.0 fail-fast: false with: grafana_version: ${{ matrix.grafana_version }} browsers: "chromium"