Skip to content

Deploy to Beta Environment #92

Deploy to Beta Environment

Deploy to Beta Environment #92

Workflow file for this run

name: Deploy to Beta Environment
on:
workflow_dispatch:
inputs:
deploy_to_npm:
description: "Deploy to NPM"
type: boolean
default: false
force_deploy_confirmation:
description: "Force deploy (bypass ALL PR checks). Type 'FORCE_DEPLOY' to confirm"
type: string
default: ""
required: false
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
id-token: write # allows the JWT to be requested from GitHub's OIDC provider
contents: read # This is required for actions/checkout
pull-requests: read # This is required to get PR information
env:
NODE_OPTIONS: '--no-warnings'
jobs:
validate-actor:
# Allow only to run from branches and not tags
if: ${{ startsWith(github.ref, 'refs/heads/') }}
uses: ./.github/workflows/validate-actor.yml
with:
team_names: 'js-sdk,integrations'
secrets:
PAT: ${{ secrets.PAT }}
find-pr:
name: Find PR for Current Branch
needs: validate-actor
runs-on: [self-hosted, Linux, X64]
outputs:
pr_number: ${{ steps.pr-info.outputs.pr_number }}
pr_url: ${{ steps.pr-info.outputs.pr_url }}
force_deploy_mode: ${{ steps.check-force-deploy.outputs.force_deploy_mode }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Check force deploy mode
id: check-force-deploy
run: |
if [ "${{ inputs.force_deploy_confirmation }}" = "FORCE_DEPLOY" ]; then
echo "force_deploy_mode=true" >> $GITHUB_OUTPUT
echo "⚠️ FORCE DEPLOY MODE ENABLED - All PR validation checks will be skipped"
else
echo "force_deploy_mode=false" >> $GITHUB_OUTPUT
fi
- name: Find PR for current branch
id: pr-info
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const branch = context.ref.replace('refs/heads/', '');
console.log(`Finding PRs for branch: ${branch}`);
const { data: pullRequests } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
head: `${context.repo.owner}:${branch}`,
base: 'develop',
state: 'open'
});
if (pullRequests.length > 0) {
const pr = pullRequests[0];
const prNumber = pr.number.toString();
const prUrl = pr.html_url;
console.log(`Found PR #${prNumber} for branch ${branch}: ${prUrl}`);
core.setOutput('pr_number', prNumber);
core.setOutput('pr_url', prUrl);
} else {
core.setFailed(`No open PR found for branch ${branch} targeting develop branch`);
}
validate-pr:
name: Validate PR Status
needs: find-pr
if: ${{ needs.find-pr.outputs.force_deploy_mode != 'true' }}
runs-on: [self-hosted, Linux, X64]
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Validate PR
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const prNumber = '${{ needs.find-pr.outputs.pr_number }}';
console.log(`Validating PR #${prNumber}...`);
// Get detailed PR information
const { data: prDetails } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
// Check if PR is in draft state
if (prDetails.draft) {
core.setFailed(`PR #${prNumber} is in draft state. Please mark it as ready for review before deploying.`);
return;
}
// Check if PR is mergeable
if (prDetails.mergeable === false) {
core.setFailed(`PR #${prNumber} is not in a mergeable state. Please resolve conflicts before deploying.`);
return;
}
// Check mergeable_state
if (prDetails.mergeable_state !== 'clean') {
let reason = '';
if (prDetails.mergeable_state === 'blocked') {
reason = 'required checks or approvals are missing';
} else if (prDetails.mergeable_state === 'dirty') {
reason = 'there are merge conflicts';
} else if (prDetails.mergeable_state === 'unstable') {
reason = 'required checks are failing';
} else {
reason = `the mergeable state is "${prDetails.mergeable_state}"`;
}
core.setFailed(`PR #${prNumber} is not ready to merge: ${reason}. Please resolve all issues before deploying.`);
return;
}
console.log(`PR #${prNumber} is in a clean mergeable state. All requirements satisfied.`);
get-deploy-inputs:
name: Get Deploy Inputs
needs: [find-pr, validate-pr]
if: |
always() &&
needs.find-pr.result == 'success' &&
(needs.find-pr.outputs.force_deploy_mode == 'true' || needs.validate-pr.result == 'success')
runs-on: [self-hosted, Linux, X64]
outputs:
version_suffix: ${{ steps.deploy-inputs.outputs.version_suffix }}
beta_identifier: ${{ steps.deploy-inputs.outputs.beta_identifier }}
beta_identifier_for_automation_tests: ${{ steps.deploy-inputs.outputs.beta_identifier_for_automation_tests }}
trigger_source: ${{ steps.set-trigger-source.outputs.trigger_source }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Set trigger source
id: set-trigger-source
run: |
TRIGGER_BASE="Triggered via PR <${{ needs.find-pr.outputs.pr_url }}|#${{ needs.find-pr.outputs.pr_number }}> by <${{ github.server_url }}/${{ github.actor }}|${{ github.actor }}>"
if [ "${{ needs.find-pr.outputs.force_deploy_mode }}" = "true" ]; then
echo "trigger_source=${TRIGGER_BASE} [⚠️ FORCE DEPLOY]" >> $GITHUB_OUTPUT
else
echo "trigger_source=${TRIGGER_BASE}" >> $GITHUB_OUTPUT
fi
- name: Extract deploy inputs
id: deploy-inputs
shell: bash
run: |
# Suffix the short commit hash with the PR number
SHA_SHORT=$(echo ${{ github.sha }} | cut -c1-7)
# The version_suffix is structured as "beta.pr.<PR_NUMBER>.<SHORT_COMMIT_HASH>".
# - "beta.pr." is a fixed prefix indicating a beta release for a pull request.
# - <PR_NUMBER> is the number of the pull request associated with the branch.
# - <SHORT_COMMIT_HASH> is the first 7 characters of the commit hash for traceability.
echo "version_suffix=beta.pr.${{ needs.find-pr.outputs.pr_number }}.$SHA_SHORT" >> $GITHUB_OUTPUT
echo "beta_identifier=PR-${{ needs.find-pr.outputs.pr_number }}/$SHA_SHORT" >> $GITHUB_OUTPUT
echo "beta_identifier_for_automation_tests=pr-${{ needs.find-pr.outputs.pr_number }}-$SHA_SHORT" >> $GITHUB_OUTPUT
deploy-cdn:
name: Deploy to CDN
uses: ./.github/workflows/deploy.yml
needs: get-deploy-inputs
with:
environment: beta
bugsnag_release_stage: beta
s3_dir_path: beta/${{ needs.get-deploy-inputs.outputs.beta_identifier }}/v3
s3_dir_path_legacy: beta/${{ needs.get-deploy-inputs.outputs.beta_identifier }}/v1.1
version_suffix: ${{ needs.get-deploy-inputs.outputs.version_suffix }}
trigger_source: ${{ needs.get-deploy-inputs.outputs.trigger_source }}
secrets:
AWS_ACCOUNT_ID: ${{ secrets.AWS_PROD_ACCOUNT_ID }}
AWS_S3_BUCKET_NAME: ${{ secrets.AWS_PROD_S3_BUCKET_NAME }}
AWS_S3_SYNC_ROLE: ${{ secrets.AWS_PROD_S3_SYNC_ROLE }}
AWS_CF_DISTRIBUTION_ID: ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }}
BUGSNAG_API_KEY: ${{ secrets.RS_PROD_BUGSNAG_API_KEY }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_RELEASE_CHANNEL_ID: ${{ secrets.SLACK_RELEASE_CHANNEL_ID_NON_PROD }}
deploy-npm:
name: Deploy to NPM
uses: ./.github/workflows/deploy-npm.yml
needs: get-deploy-inputs
if: ${{ inputs.deploy_to_npm }}
with:
environment: beta
bugsnag_release_stage: beta
version_suffix: ${{ needs.get-deploy-inputs.outputs.version_suffix }}
base_version: develop
head_version: ${{ github.ref_name }}
trigger_source: ${{ needs.get-deploy-inputs.outputs.trigger_source }}
secrets:
RS_PROD_BUGSNAG_API_KEY: ${{ secrets.RS_PROD_BUGSNAG_API_KEY }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_RELEASE_CHANNEL_ID: ${{ secrets.SLACK_RELEASE_CHANNEL_ID_NON_PROD }}
deploy-sanity-suite:
name: Deploy sanity suite
needs: [get-deploy-inputs]
uses: ./.github/workflows/deploy-sanity-suite.yml
with:
environment: 'beta'
beta_identifier: ${{ needs.get-deploy-inputs.outputs.beta_identifier }}
trigger_source: ${{ needs.get-deploy-inputs.outputs.trigger_source }}
secrets:
AWS_ACCOUNT_ID: ${{ secrets.AWS_PROD_ACCOUNT_ID }}
AWS_S3_BUCKET_NAME: ${{ secrets.AWS_PROD_S3_BUCKET_NAME }}
AWS_S3_SYNC_ROLE: ${{ secrets.AWS_PROD_S3_SYNC_ROLE }}
AWS_CF_DISTRIBUTION_ID: ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }}
SANITY_SUITE_WRITE_KEY: ${{ secrets.SANITY_SUITE_PROD_WRITE_KEY }}
SANITY_SUITE_DATAPLANE_URL: ${{ secrets.SANITY_SUITE_PROD_DATAPLANE_URL }}
SANITY_SUITE_CONFIG_SERVER_HOST: ${{ secrets.SANITY_SUITE_PROD_CONFIG_SERVER_HOST }}
BUGSNAG_API_KEY: ${{ secrets.RS_PROD_BUGSNAG_API_KEY }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_RELEASE_CHANNEL_ID: ${{ secrets.SLACK_RELEASE_CHANNEL_ID_NON_PROD }}
run-e2e-regression-test-suites:
uses: ./.github/workflows/run-e2e-regression-test-suites.yml
name: Run E2E Regression Test Suites
needs: [get-deploy-inputs, deploy-sanity-suite, deploy-cdn]
with:
environment: beta
sanity_test_suite_url: https://cdn.rudderlabs.com/sanity-suite/beta/${{ needs.get-deploy-inputs.outputs.beta_identifier }}
build_source_id: ${{ needs.get-deploy-inputs.outputs.beta_identifier_for_automation_tests }}
trigger_source: ${{ needs.get-deploy-inputs.outputs.trigger_source }}
secrets:
PAT: ${{ secrets.PAT }}