feat(shop): product dialog popup instead of page navigation #99
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Dynamic mirrord Preview Environment: detects changed services, builds images, | |
| # and starts a preview for each. Posts a single aggregated PR comment + Slack message. | |
| # Uses metalbear-co/mirrord-preview GitHub Action. | |
| # Requires: GCP_WIF_PROVIDER + GCP_SERVICE_ACCOUNT + SLACK_WEBHOOK_URL secrets, | |
| # mirrord operator with Enterprise license. | |
| name: Preview Environment - Shop (Dynamic) | |
| on: | |
| pull_request: | |
| types: [opened, reopened, closed] | |
| concurrency: | |
| group: preview-shop-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| env: | |
| PREVIEW_KEY: "${{ github.head_ref }}" | |
| jobs: | |
| # ─── Job 1: Detect which services changed ─── | |
| detect-changes: | |
| name: Detect changed services | |
| if: github.event.action != 'closed' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.build-matrix.outputs.matrix }} | |
| has_changes: ${{ steps.build-matrix.outputs.has_changes }} | |
| services_list: ${{ steps.build-matrix.outputs.services_list }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect changed services via git diff | |
| id: build-matrix | |
| run: | | |
| CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) | |
| CHANGES='[]' | |
| for path in $(jq -r '.services[].path' .github/preview-services.json); do | |
| name=$(jq -r --arg p "$path" '.services[] | select(.path == $p) | .name' .github/preview-services.json) | |
| if echo "$CHANGED_FILES" | grep -q "^${path}/"; then | |
| CHANGES=$(echo "$CHANGES" | jq -c --arg n "$name" '. + [$n]') | |
| fi | |
| done | |
| MATRIX=$(jq -c --argjson changes "$CHANGES" \ | |
| '{include: [.services[] | select(.name as $n | $changes | index($n))]}' \ | |
| .github/preview-services.json) | |
| SERVICES_LIST=$(jq -r --argjson changes "$CHANGES" \ | |
| '[.services[] | select(.name as $n | $changes | index($n)) | .name] | join(", ")' \ | |
| .github/preview-services.json) | |
| COUNT=$(echo "$CHANGES" | jq 'length') | |
| echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" | |
| echo "services_list=$SERVICES_LIST" >> "$GITHUB_OUTPUT" | |
| if [ "$COUNT" -gt 0 ]; then | |
| echo "has_changes=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "has_changes=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "Changed services ($COUNT): $SERVICES_LIST" | |
| # ─── Job 2: Build image + start preview for each changed service ─── | |
| build-and-preview: | |
| name: Build & Preview (${{ matrix.name }}) | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.has_changes == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| matrix: ${{ fromJSON(needs.detect-changes.outputs.matrix) }} | |
| fail-fast: false | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: ${{ matrix.path }} | |
| file: ${{ matrix.path }}/Dockerfile | |
| platforms: linux/amd64 | |
| push: true | |
| tags: ghcr.io/metalbear-co/playground-${{ matrix.name }}:preview-${{ github.head_ref }}-${{ github.sha }} | |
| build-args: ${{ matrix.build_args }} | |
| cache-from: type=gha,scope=${{ matrix.name }} | |
| cache-to: type=gha,scope=${{ matrix.name }},mode=max | |
| - name: Authenticate to GCP via Workload Identity Federation | |
| uses: google-github-actions/auth@v2 | |
| with: | |
| workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }} | |
| service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} | |
| - name: Get GKE credentials | |
| uses: google-github-actions/get-gke-credentials@v2 | |
| with: | |
| cluster_name: playground-cluster-1 | |
| location: us-central1-c | |
| project_id: playground-383912 | |
| - name: Start mirrord preview | |
| uses: metalbear-co/mirrord-preview@master | |
| with: | |
| action: start | |
| target: deployment/${{ matrix.deployment }} | |
| namespace: ${{ matrix.namespace }} | |
| image: ghcr.io/metalbear-co/playground-${{ matrix.name }}:preview-${{ github.head_ref }}-${{ github.sha }} | |
| mode: steal | |
| filter: 'baggage:\s*[^\n]*\bmirrord={{ key }}\b' | |
| ttl_mins: '120' | |
| key: ${{ env.PREVIEW_KEY }} | |
| extra_config: ${{ matrix.extra_config }} | |
| - name: Write result artifact | |
| if: success() | |
| run: | | |
| mkdir -p /tmp/preview-results | |
| echo '{"name":"${{ matrix.name }}"}' > /tmp/preview-results/${{ matrix.name }}.json | |
| - name: Upload result artifact | |
| if: success() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: preview-result-${{ matrix.name }} | |
| path: /tmp/preview-results/${{ matrix.name }}.json | |
| retention-days: 1 | |
| # ─── Job 3: Post aggregated PR comment + Slack notification ─── | |
| notify: | |
| name: Post notifications | |
| needs: [detect-changes, build-and-preview] | |
| if: needs.detect-changes.outputs.has_changes == 'true' && !cancelled() | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - name: Download all result artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: preview-result-* | |
| path: /tmp/preview-results | |
| merge-multiple: true | |
| - name: Post PR comment with preview details | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const key = `${{ env.PREVIEW_KEY }}`; | |
| const url = 'https://playground.metalbear.dev/shop'; | |
| // Read all result files to get list of successfully previewed services | |
| const dir = '/tmp/preview-results'; | |
| let services = []; | |
| try { | |
| const files = fs.readdirSync(dir).filter(f => f.endsWith('.json')); | |
| services = files.map(f => JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8'))); | |
| } catch (e) { | |
| console.log('No preview results found, some or all previews may have failed.'); | |
| } | |
| const serviceList = services.length > 0 | |
| ? services.map(s => `| \`${s.name}\` | \`deployment/${s.name}\` |`).join('\n') | |
| : '| _(no previews succeeded)_ | |'; | |
| const body = [ | |
| '## mirrord Preview Environment - Metal Mart', | |
| '', | |
| 'Preview environments are running for this PR.', | |
| '', | |
| '| Service | Target |', | |
| '|---|---|', | |
| serviceList, | |
| '', | |
| '| | |', | |
| '|---|---|', | |
| `| **Preview URL** | [${url}](${url}) |`, | |
| `| **Header** | \`baggage: mirrord=${key}\` |`, | |
| '', | |
| 'To send traffic to this preview:', | |
| `- Use the [mirrord Browser Extension](https://metalbear.com/mirrord/docs/using-mirrord/browser-extension) and set the header for this URL, or`, | |
| `- \`curl -H "baggage: mirrord=${key}" ${url}\``, | |
| '', | |
| '*Preview is stopped when the PR is merged or closed.*', | |
| ].join('\n'); | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(c => c.body && c.body.includes('## mirrord Preview Environment - Metal Mart')); | |
| if (botComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| } | |
| - name: Post preview link to Slack | |
| uses: slackapi/slack-github-action@v1.27.0 | |
| with: | |
| payload: | | |
| { | |
| "blocks": [ | |
| { | |
| "type": "header", | |
| "text": { "type": "plain_text", "text": "mirrord Preview Environment - Metal Mart" } | |
| }, | |
| { | |
| "type": "section", | |
| "fields": [ | |
| { "type": "mrkdwn", "text": "*PR:*\n<${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }}>" }, | |
| { "type": "mrkdwn", "text": "*Services:*\n${{ needs.detect-changes.outputs.services_list }}" }, | |
| { "type": "mrkdwn", "text": "*Preview URL:*\n<https://playground.metalbear.dev/shop|Open Preview>" }, | |
| { "type": "mrkdwn", "text": "*Header:*\n`baggage: mirrord=${{ env.PREVIEW_KEY }}`" } | |
| ] | |
| } | |
| ] | |
| } | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} | |
| SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK | |
| # ─── Job 4: Stop all previews on merge/close ─── | |
| preview-stop: | |
| name: Stop all previews on merge/close | |
| if: github.event.action == 'closed' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Authenticate to GCP via Workload Identity Federation | |
| uses: google-github-actions/auth@v2 | |
| with: | |
| workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }} | |
| service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} | |
| - name: Get GKE credentials | |
| uses: google-github-actions/get-gke-credentials@v2 | |
| with: | |
| cluster_name: playground-cluster-1 | |
| location: us-central1-c | |
| project_id: playground-383912 | |
| - name: Stop mirrord preview | |
| uses: metalbear-co/mirrord-preview@master | |
| with: | |
| action: stop | |
| key: ${{ env.PREVIEW_KEY }} |