name: PR-Deployment on: workflow_dispatch: inputs: build_service: description: | Name of a single service to build(in small letters), eg: api or frontend etc. backend:sevice-name to build service. If what ever image is not built, it'll be deployed from latest release. Options: none/all/service-name/backend:{app1/app1,app2,app3/all} required: false default: none env_flavour: description: 'Which env to build. Values: foss/ee' required: false jobs: create-vcluster-pr: runs-on: ubuntu-latest env: build_service: ${{ github.event.inputs.build_service }} env_flavour: ${{ github.event.inputs.env_flavour }} steps: - name: Checkout Code uses: actions/checkout@v2 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.OR_PR_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.OR_PR_AWS_SECRET_ACCESS_KEY }} aws-region: ${{ secrets.OR_PR_AWS_DEFAULT_REGION}} - name: Setting up env variables run: | # Fetching details open/draft PR for current branch PR_DATA=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ "https://api.github.com/repos/${{ github.repository }}/pulls" \ | jq -r --arg BRANCH "${{ github.ref_name }}" '.[] | select((.head.ref==$BRANCH) and (.state=="open") and (.draft==true or .draft==false))') # Extracting PR number PR_NUMBER=$(echo "$PR_DATA" | jq -r '.number' | head -n 1) if [ -z $PR_NUMBER ]; then echo "No PR found for ${{ github.ref_name}}" exit 100 fi echo "PR_NUMBER_PRE=$PR_NUMBER" >> $GITHUB_ENV PR_NUMBER=pr-$PR_NUMBER if [ $env_flavour == "ee" ]; then PR_NUMBER=$PR_NUMBER-ee fi echo "PR number: $PR_NUMBER" echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV # Extracting PR status (open, closed, merged) PR_STATUS=$(echo "$PR_DATA" | jq -r '.state' | head -n 1) echo "PR status: $PR_STATUS" echo "PR_STATUS=$PR_STATUS" >> $GITHUB_ENV - name: Install vCluster CLI run: | # Replace with the command to install vCluster CLI curl -s -L "https://github.com/loft-sh/vcluster/releases/download/v0.16.4/vcluster-linux-amd64" -o /usr/local/bin/vcluster chmod +x /usr/local/bin/vcluster - uses: azure/k8s-set-context@v1 with: method: kubeconfig kubeconfig: ${{ secrets.PR_KUBECONFIG }} # Use content of kubeconfig in secret. id: setcontext - name: Check existing vcluster id: vcluster_exists continue-on-error: true run: | if ! $(vcluster list | grep $PR_NUMBER &> /dev/null); then echo "no cluster found for $PR_NUMBER" echo "::set-output name=failed::true" exit 100 fi DOMAIN_NAME=${PR_NUMBER}-vcluster.${{ secrets.OR_PR_DOMAIN_NAME }} vcluster connect ${PR_NUMBER}-vcluster --update-current=false --server=https://$DOMAIN_NAME mv kubeconfig.yaml /tmp/kubeconfig.yaml - name: Get LoadBalancer IP if: steps.vcluster_exists.outputs.failed == 'true' id: lb-ip run: | # LB_IP=$(kubectl get svc ingress-ingress-nginx-controller -n default -o jsonpath='{.status.loadBalancer.ingress[0].ip}') LB_IP=$(kubectl get svc ingress-ingress-nginx-controller -n default -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') echo "::set-output name=ip::$LB_IP" - name: Create vCluster if: steps.vcluster_exists.outputs.failed == 'true' run: | # Replace with the actual command to create a vCluster pwd cd scripts/pr-env/ bash create.sh ${PR_NUMBER}.${{ secrets.OR_PR_DOMAIN_NAME }} cp kubeconfig.yaml /tmp/ - name: Update AWS Route53 Record if: steps.vcluster_exists.outputs.failed == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.OR_PR_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.OR_PR_AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ${{ secrets.OR_PR_AWS_DEFAULT_REGION }} run: | DOMAIN_NAME_1=$PR_NUMBER-vcluster.${{ secrets.OR_PR_DOMAIN_NAME }} DOMAIN_NAME_2=$PR_NUMBER.${{ secrets.OR_PR_DOMAIN_NAME }} cat < route53-changes.json { "Comment": "Create record set for VCluster", "Changes": [ { "Action": "CREATE", "ResourceRecordSet": { "Name": "$DOMAIN_NAME_1", "Type": "CNAME", "TTL": 300, "ResourceRecords": [{ "Value": "${{ steps.lb-ip.outputs.ip }}" }] } }, { "Action": "CREATE", "ResourceRecordSet": { "Name": "$DOMAIN_NAME_2", "Type": "CNAME", "TTL": 300, "ResourceRecords": [{ "Value": "${{ steps.lb-ip.outputs.ip }}" }] } } ] } EOF # NEW_IP=${{ steps.lb-ip.outputs.ip }} # Get the current IP address associated with the domain CURRENT_IP=$(dig +short $DOMAIN_NAME_1 @1.1.1.1) echo "current ip: $CURRENT_IP" # Check if the domain has no IP association or if the IPs are different if [ -z "$CURRENT_IP" ] || [ "$CURRENT_IP" != "$NEW_IP" ]; then aws route53 change-resource-record-sets --hosted-zone-id ${{ secrets.OR_PR_HOSTED_ZONE_ID }} --change-batch file://route53-changes.json fi - name: Wait for DNS Propagation if: steps.vcluster_exists.outputs.failed == 'true' env: EXPECTED_IP: ${{ steps.lb-ip.outputs.ip }} run: | DOMAIN_NAME="$PR_NUMBER-vcluster.${{ secrets.OR_PR_DOMAIN_NAME }}" MAX_ATTEMPTS=30 attempt=1 until [[ $attempt -gt $MAX_ATTEMPTS ]] do # Use dig to query DNS records DNS_RESULT=$(dig +short $DOMAIN_NAME @1.1.1.1) # Check if DNS result is empty if [ -z "$DNS_RESULT" ]; then echo "No IP or CNAME records found for $DOMAIN_NAME." else echo "DNS records found for $DOMAIN_NAME:" echo "$DNS_RESULT" break fi echo "Waiting for DNS propagation... Attempt $attempt of $MAX_ATTEMPTS" ((attempt++)) sleep 20 done if [[ $attempt -gt $MAX_ATTEMPTS ]]; then echo "DNS propagation check failed for $DOMAIN_NAME after $MAX_ATTEMPTS attempts." exit 1 fi - name: Install openreplay if: steps.vcluster_exists.outputs.failed == 'true' env: KUBECONFIG: /tmp/kubeconfig.yaml run: | DOMAIN_NAME=$PR_NUMBER.${{ secrets.OR_PR_DOMAIN_NAME }} cd scripts/helmcharts sed -i "s/domainName: \"\"/domainName: \"${DOMAIN_NAME}\"/g" vars.yaml # If ee cluster, enable the following if [ $env_flavour == "ee" ]; then # Explanation for the sed command: # /clickhouse:/: Matches lines containing "clickhouse:". # {:a: Starts a block with label 'a'. # n;: Reads the next line. # /enabled:/s/false/true/: If the line contains 'enabled:', replace 'false' with 'true'. # t done;: If the substitution was made, branch to label 'done'. # ba;: Go back to label 'a' if no substitution was made. # :done}: Label 'done', where the script goes after a successful substitution. sed -i '/clickhouse:/{:a;n;/enabled:/s/false/true/;t done; ba; :done}' vars.yaml sed -i '/kafka:/{:a;n;/# enabled:/s/# enabled: .*/enabled: true/;t done; ba; :done}' vars.yaml sed -i '/redis:/{:a;n;/enabled:/s/true/false/;t done; ba; :done}' vars.yaml sed -i "s/enterpriseEditionLicense: \"\"/enterpriseEditionLicense: \"${{ secrets.EE_LICENSE_KEY }}\"/g" vars.yaml sed -i "s/domainName: \"\"/domainName: \"${DOMAIN_NAME}\"/g" vars.yaml fi helm upgrade -i databases -n db ./databases -f vars.yaml --create-namespace --wait -f ../pr-env/resources.yaml helm upgrade -i openreplay -n app ./openreplay -f vars.yaml --create-namespace --set ingress-nginx.enabled=false -f ../pr-env/resources.yaml --wait - name: Build and deploy application env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} env: ${{ github.event.inputs.env_flavour }} run: | set -x app_name=${{github.event.inputs.build_service}} echo "building and deploying $app_name" docker login ${{ secrets.OSS_REGISTRY_URL }} -u ${{ secrets.OSS_DOCKER_USERNAME }} -p "${{ secrets.OSS_REGISTRY_TOKEN }}" export KUBECONFIG=/tmp/kubeconfig.yaml function build_and_deploy { apps_to_build=$1 case $apps_to_build in backend*) echo "Building backend build" cd $GITHUB_WORKSPACE/backend components=() if [ $apps_to_build == "backend:all" ]; then # Append all folder names from 'cmd/' directory to the array for folder in cmd/*/; do # Use basename to extract the folder name without path folder_name=$(basename "$folder") components+=("$folder_name") done else # "${apps_to_build#*:}" :: Strip backend: and output app1,app2,app3 to read -ra IFS=',' read -ra components <<< "${apps_to_build#*:}" fi echo "Building components: " ${components[@]} for component in "${components[@]}"; do if [ $(docker manifest inspect ${DOCKER_REPO}/$component:${IMAGE_TAG} > /dev/null) ]; then echo Image present upstream. Skipping build: $component else echo "Building backend:$component" PUSH_IMAGE=1 bash -x ./build.sh $env $component fi kubectl set image -n app deployment/$component-openreplay $component=${DOCKER_REPO}/$component:${IMAGE_TAG} done ;; chalice|api) echo "Chalice build" component=chalice cd $GITHUB_WORKSPACE/api || (Nothing to build: $component; exit 100) if [ $(docker manifest inspect ${DOCKER_REPO}/$component:${IMAGE_TAG} > /dev/null) ]; then echo Image present upstream. Skipping build: $component else echo "Building backend:$component" PUSH_IMAGE=1 bash -x ./build.sh $env $component fi kubectl set image -n app deployment/$component-openreplay $component=${DOCKER_REPO}/$component:${IMAGE_TAG} ;; *) echo "$apps_to_build build" cd $GITHUB_WORKSPACE/$apps_to_build || (Nothing to build: $apps_to_build; exit 100) component=$apps_to_build if [ $(docker manifest inspect ${DOCKER_REPO}/$component:${IMAGE_TAG} > /dev/null) ]; then echo Image present upstream. Skipping build: $component else echo "Building backend:$component" PUSH_IMAGE=1 bash -x ./build.sh $env $component fi kubectl set image -n app deployment/$apps_to_build-openreplay $apps_to_build=${DOCKER_REPO}/$apps_to_build:${IMAGE_TAG} ;; esac } case $app_name in all) build_and_deploy "backend:all" build_and_deploy "frontend" build_and_deploy "chalice" build_and_deploy "sourcemapreader" build_and_deploy "assist-stats" ;; none) echo "Nothing to build" ;; *) build_and_deploy $app_name ;; esac - name: Sent results to slack if: steps.vcluster_exists.outputs.failed == 'true' env: SLACK_BOT_TOKEN: ${{ secrets.OR_PR_SLACK_BOT_TOKEN }} SLACK_CHANNEL: ${{ secrets.OR_PR_SLACK_CHANNEL }} run: | echo hi ${{ steps.vcluster_exists.outputs.failed }} DOMAIN_NAME=https://$PR_NUMBER.${{ secrets.OR_PR_DOMAIN_NAME }} # Variables PR_NUMBER=https://github.com/${{ github.repository }}/pull/${PR_NUMBER_PRE} BRANCH_NAME=${{ github.ref_name }} ORIGIN=$DOMAIN_NAME ASSETS_HOST=$DOMAIN_NAME/assets API_EDP=$DOMAIN_NAME/api INGEST_POINT=$DOMAIN_NAME/ingest # File to be uploaded FILE_PATH="/tmp/kubeconfig.yaml" if [! -f $FILE_PATH ]; then echo "Kubeconfig file not found: $FILE_PATH" exit 100 fi # Form the message payload PAYLOAD=$(cat < /dev/null # Upload the file to Slack curl -F file=@"$FILE_PATH" -F channels="$SLACK_CHANNEL" -F token="$SLACK_BOT_TOKEN" https://slack.com/api/files.upload > /dev/null # - name: Cleanup # if: always() # run: | # # Add any cleanup commands if necessary # - name: Debug Job # # if: ${{ failure() }} # uses: mxschmitt/action-tmate@v3 # env: # DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} # IMAGE_TAG: ${{ github.sha }}-ee # ENVIRONMENT: staging # with: # iimit-access-to-actor: true