diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml new file mode 100644 index 0000000..b97aaad --- /dev/null +++ b/.github/workflows/deploy-staging.yml @@ -0,0 +1,198 @@ +name: Deploy to Staging + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to deploy (default: main)' + required: false + type: string + default: 'main' + service_name: + description: 'GCP App Engine service name to deploy to (default: wildebackyard-web-staging)' + required: false + type: string + default: 'wildebackyard-web-staging' + +env: + GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} + GCP_REGION: us-central1 + PYTHON_VERSION: '3.12' + SERVICE_NAME: ${{ inputs.service_name || 'wildebackyard-web-staging' }} + +jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + + permissions: + contents: read + + env: + DJANGO_SECRET_KEY: test-secret-key-for-ci + DJANGO_SETTINGS_MODULE: config.settings.local + DJANGO_ADMIN_URL: admin/ + BACKEND_API_URL: https://wildebackyard-api-dot-wildepod-339517.wl.r.appspot.com + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch || 'main' }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: Install dependencies + run: | + uv sync + uv pip install -r requirements-dev.txt + + - name: Run tests + run: | + uv run pytest --verbose --tb=short + + build: + name: Build Validation + runs-on: ubuntu-latest + needs: test + + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch || 'main' }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + + - name: Validate GCP credentials secret + env: + GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }} + run: | + if [ -z "$GCP_SA_KEY" ]; then + echo "Error: GCP_SA_KEY secret is not set or is empty" + echo "Please configure the GCP_SA_KEY secret in your repository settings:" + echo "1. Go to Settings → Secrets and variables → Actions" + echo "2. Add a new secret named 'GCP_SA_KEY'" + echo "3. Paste the contents of your service account JSON key" + exit 1 + fi + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.GCP_SA_KEY }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + + - name: Validate app.yaml + run: | + if [ ! -f "app.yaml" ]; then + echo "Error: app.yaml not found" + exit 1 + fi + python -c "import yaml; yaml.safe_load(open('app.yaml'))" + echo "✓ app.yaml is valid" + + deploy: + name: Deploy to App Engine Staging + runs-on: ubuntu-latest + needs: [test, build] + environment: + name: staging + + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch || 'main' }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.GCP_SA_KEY }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Update version.py with deployment commit hash + shell: bash + run: | + SHORT_HASH=$(git rev-parse --short HEAD) + RELEASE_TAG=$(git describe --tags --exact-match 2>/dev/null || echo "staging") + export SHORT_HASH RELEASE_TAG + python3 << 'PYTHON' + import os + short_hash = os.environ.get('SHORT_HASH', 'unknown') + release_tag = os.environ.get('RELEASE_TAG', 'unknown') + with open('config/version.py', 'w') as f: + f.write('"""\n') + f.write('Version information for the application.\n') + f.write('This file is automatically updated by the GitHub Actions workflow during deployment.\n') + f.write('"""\n') + f.write('\n') + f.write('VERSION = {\n') + f.write(f' "commit_hash": "{short_hash}",\n') + f.write(f' "release_tag": "{release_tag}",\n') + f.write('}\n') + print(f'✓ Updated version.py with commit hash: {short_hash} and release tag: {release_tag}') + PYTHON + + - name: Collect static files + continue-on-error: true + env: + DJANGO_SECRET_KEY: ${{ secrets.DJANGO_SECRET_KEY }} + CLOUD_SQL_DATABASE_URL_STAGING: ${{ secrets.CLOUD_SQL_DATABASE_URL_STAGING }} + run: | + uv run python manage.py collectstatic --noinput --settings=config.settings.staging || echo "Static collection skipped" + + - name: Deploy to App Engine + run: | + gcloud app deploy \ + --project=${{ env.GCP_PROJECT_ID }} \ + --verbosity=info \ + --no-promote \ + --version=$(echo $GITHUB_SHA | cut -c1-7) \ + --appyaml=app.yaml + + - name: Get deployment info + id: deployment + run: | + VERSION=$(echo $GITHUB_SHA | cut -c1-7) + URL="https://${VERSION}-dot-${{ env.SERVICE_NAME }}-dot-${{ env.GCP_PROJECT_ID }}.appspot.com" + echo "url=$URL" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "✓ Deployed version ${VERSION} to ${{ env.SERVICE_NAME }}" + echo "✓ URL: ${URL}"