diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index cf2a25a516a96790bc20cbb32246600d34aa97bb..1fe620c1de9bf943636378c055878bfa4b16aa3d 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -251,3 +251,11 @@ jobs: steps: - run: | echo "Didn't run due to conditional filtering" + + pr-env: + needs: [build] + if: | + !cancelled() && + contains(github.event.pull_request.labels.*.name, 'PR-Env') + uses: ./.github/workflows/pr-env.yml + secrets: inherit diff --git a/.github/workflows/pr-env-close-unlabel.yml b/.github/workflows/pr-env-close-unlabel.yml new file mode 100644 index 0000000000000000000000000000000000000000..0d2ff1673b007d53fa7ac7b1f3ef0fa4539b19f1 --- /dev/null +++ b/.github/workflows/pr-env-close-unlabel.yml @@ -0,0 +1,15 @@ +name: PR Closed or Unlabeled + +on: + pull_request: + types: [ closed, unlabeled ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + pr-env: + if: ${{ github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'PR-Env') || github.event.label.name == 'PR-Env' }} + uses: ./.github/workflows/pr-env-destroy.yml + secrets: inherit diff --git a/.github/workflows/pr-env-destroy.yml b/.github/workflows/pr-env-destroy.yml new file mode 100644 index 0000000000000000000000000000000000000000..273fdb81d21a6926bb88fc82a3f36ebacdf58a27 --- /dev/null +++ b/.github/workflows/pr-env-destroy.yml @@ -0,0 +1,71 @@ +name: CI for PR Review ENV Destroy +run-name: Destroying Dynamic PR Environment for ${{ github.ref_name }} by @${{ github.actor }} + +on: + workflow_call: + +jobs: + destroy_pr: + runs-on: ubuntu-latest + name: PR Review ENV Destroy + permissions: + id-token: write + contents: read + steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Tailscale + uses: tailscale/github-action@v2 + with: + oauth-client-id: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_CLIENT_ID }} + oauth-secret: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_SECRET }} + tags: tag:ci + version: 1.50.1 + sha256sum: d9fe6b480fb5078f0aa57dace686898dda7e2a768884271159faa74846bfb576 + - name: Create OIDC Token + id: create-oidc-token + shell: bash + run: | + export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${{ secrets.PR_ENV_K8S_AUDIENCE }}" + IDTOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" -H "Accept: application/json; api-version=2.0" "$OIDC_URL_WITH_AUDIENCE" | jq -r .value) + echo "::add-mask::${IDTOKEN}" + echo "idToken=${IDTOKEN}" >>$GITHUB_OUTPUT + - name: Setup Kube Context + uses: azure/k8s-set-context@v2 + with: + method: kubeconfig + kubeconfig: | + kind: Config + apiVersion: v1 + current-context: default + clusters: + - name: default + cluster: + certificate-authority-data: ${{ secrets.PR_ENV_K8S_CERTIFICATE_AUTHORITY_DATA }} + server: ${{ secrets.PR_ENV_K8S_SERVER }} + users: + - name: oidc-token + user: + token: ${{ steps.create-oidc-token.outputs.IDTOKEN }} + contexts: + - name: default + context: + cluster: default + namespace: default + user: oidc-token + - name: Destroy PR Review ENV + run: | + kubectl delete metabase -n hosting-pr${{ github.event.number }} hosting-pr${{ github.event.number }} + kubectl delete ns hosting-pr${{ github.event.number }} + - name: Setup psql client + run: | + sudo apt-get update + sudo apt-get install -y postgresql-client + - name: Drop app database if exists + run: | + PGPASSWORD='${{ secrets.PR_ENV_DB_PASSWORD }}' \ + psql \ + -h ${{ secrets.PR_ENV_DB_HOST }} \ + -U ${{ secrets.PR_ENV_DB_USER }} \ + -d ${{ secrets.PR_ENV_DB_NAME }} \ + -c "DROP DATABASE IF EXISTS hosting_pr${{ github.event.number }};" diff --git a/.github/workflows/pr-env-labeled.yml b/.github/workflows/pr-env-labeled.yml new file mode 100644 index 0000000000000000000000000000000000000000..6c1aa0ae9bd83591d43ecf79aafbda6fc85e4041 --- /dev/null +++ b/.github/workflows/pr-env-labeled.yml @@ -0,0 +1,17 @@ +name: PR + +on: + pull_request: + types: [ labeled ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + pr-env: + if: ${{ github.event.label.name == 'PR-Env' }} + uses: ./.github/workflows/pr-env.yml + with: + wait_for_uberjar: true + secrets: inherit diff --git a/.github/workflows/pr-env.yml b/.github/workflows/pr-env.yml new file mode 100644 index 0000000000000000000000000000000000000000..a4af86f580ee554c36b3289055e0d95dda0a41da --- /dev/null +++ b/.github/workflows/pr-env.yml @@ -0,0 +1,167 @@ +name: CI for PR Review ENV +run-name: Building Dynamic PR Environment for ${{ github.ref_name }} by @${{ github.actor }} + +on: + workflow_call: + inputs: + wait_for_uberjar: + description: "Wait for uberjar build" + required: false + type: boolean +jobs: + build: + runs-on: ubuntu-latest + name: Build Metabase Docker image + timeout-minutes: 60 + permissions: + id-token: write + contents: read + actions: read + steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.PR_ENV_IAM_ROLE }} + role-session-name: GitHub_to_AWS_via_FederatedOIDC + aws-region: us-east-1 + - name: Login to Amazon ECR + uses: aws-actions/amazon-ecr-login@v2 + with: + registries: "${{ secrets.PR_ENV_AWS_ACCOUNT_ID }}" + - name: Wait for uberjar + id: wait_for_uberjar + if: ${{ inputs.wait_for_uberjar == true }} + run: | + ## Get workflow run id for uberjar build + curl -Ls --output e2e-tests.json \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ github.repository }}/actions/workflows/e2e-tests.yml/runs?head_sha=${{ github.event.pull_request.head.sha || github.sha }} + ID=$(jq -r '.workflow_runs[0].id' e2e-tests.json) + ## Wait for uberjar build to complete + while [ true ]; do + curl -Ls --output uberjar.json \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ github.repository }}/actions/runs/${ID}/jobs?filter=latest + jq -r '.jobs[] | select(.name == "build (ee)") | .steps[] | select(.name == "Prepare uberjar artifact") | .status' uberjar.json | grep -q "completed" && break + echo "Waiting for uberjar build..." + sleep 10 + done + echo "run_id=$(jq -r '.workflow_runs[0].id' e2e-tests.json)" >> $GITHUB_OUTPUT + - name: Retrieve uberjar artifact for ee + uses: actions/download-artifact@v4 + with: + name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ inputs.wait_for_uberjar && steps.wait_for_uberjar.outputs.run_id || github.run_id }} + - name: Move uberjar to bin/docker + run: | + jar xf target/uberjar/metabase.jar + mv target/uberjar/metabase.jar bin/docker/metabase.jar + - name: Build container + uses: docker/build-push-action@v6 + with: + context: bin/docker/ + platforms: linux/amd64 + network: host + tags: ${{ secrets.PR_ENV_AWS_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/metabase-enterprise:pr${{ github.event.number }} + push: true + deploy_pr: + needs: [ build ] + runs-on: ubuntu-latest + name: PR Review ENV + permissions: + id-token: write + contents: read + steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Tailscale + uses: tailscale/github-action@v2 + with: + oauth-client-id: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_CLIENT_ID }} + oauth-secret: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_SECRET }} + tags: tag:ci + version: 1.50.1 + sha256sum: d9fe6b480fb5078f0aa57dace686898dda7e2a768884271159faa74846bfb576 + - name: Create OIDC Token + id: create-oidc-token + shell: bash + run: | + export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${{ secrets.PR_ENV_K8S_AUDIENCE }}" + IDTOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" -H "Accept: application/json; api-version=2.0" "$OIDC_URL_WITH_AUDIENCE" | jq -r .value) + echo "::add-mask::${IDTOKEN}" + echo "idToken=${IDTOKEN}" >>$GITHUB_OUTPUT + - name: Setup Kube Context + uses: azure/k8s-set-context@v2 + with: + method: kubeconfig + kubeconfig: | + kind: Config + apiVersion: v1 + current-context: default + clusters: + - name: default + cluster: + certificate-authority-data: ${{ secrets.PR_ENV_K8S_CERTIFICATE_AUTHORITY_DATA }} + server: ${{ secrets.PR_ENV_K8S_SERVER }} + users: + - name: oidc-token + user: + token: ${{ steps.create-oidc-token.outputs.IDTOKEN }} + contexts: + - name: default + context: + cluster: default + namespace: default + user: oidc-token + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.PR_ENV_IAM_ROLE }} + role-session-name: GitHub_to_AWS_via_FederatedOIDC + aws-region: us-east-1 + - name: Setup psql client + run: | + sudo apt-get update + sudo apt-get install -y postgresql-client + - name: Create app database if not exists + run: | + export PGPASSWORD='${{ secrets.PR_ENV_DB_PASSWORD }}' + export PGUSER=${{ secrets.PR_ENV_DB_USER }} + export PGHOST=${{ secrets.PR_ENV_DB_HOST }} + export PGDATABASE=${{ secrets.PR_ENV_DB_NAME }} + psql -tc "SELECT 1 FROM pg_database WHERE datname = 'hosting_pr${{ github.event.number }}'" \ + | grep -q 1 \ + || psql -c "CREATE DATABASE hosting_pr${{ github.event.number }}" + - name: Download Deployment YAML template + run: aws s3 cp s3://metabase-pr-env/metabase.yml.tmpl ./metabase.yml.tmpl + - name: Render Deployment YAML + uses: nowactions/envsubst@v1 + with: + input: ./metabase.yml.tmpl + output: ./metabase.yml + env: + IMAGE_TAG: pr${{ github.event.number }} + PR_NUMBER: ${{ github.event.number }} + MB_PREMIUM_EMBEDDING_TOKEN: ${{ secrets.PR_ENV_MB_PREMIUM_EMBEDDING_TOKEN }} + SHA: ${{ github.event.pull_request.head.sha || github.sha }} + - name: Deploy PR Review ENV + run: | + kubectl apply -f ./metabase.yml + preview_links: + runs-on: ubuntu-latest + needs: [ deploy_pr ] + steps: + - uses: marocchino/sticky-pull-request-comment@v2 + with: + recreate: true + message: | + 👋 Deploying a preview environment for commit ${{ github.event.pull_request.head.sha || github.sha }}. + ✅ Preview: + https://pr${{ github.event.number }}.coredev.metabase.com