Create new github action to handle auto creation and destruction of test VMs #89
This file contains 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
name: Create/Destroy Test VM | |
on: | |
pull_request: | |
types: [opened, reopened, synchronize, labeled, unlabeled, closed] | |
jobs: | |
create_test_vm: | |
if: | | |
( | |
( | |
github.event.action == 'opened' || | |
github.event.action == 'reopened' || | |
github.event.action == 'synchronize' | |
) && contains(github.event.pull_request.labels.*.name, 'create-test-vm') | |
) || | |
( | |
github.event.action == 'labeled' && | |
github.event.label.name == 'create-test-vm' | |
) | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout files | |
uses: actions/checkout@v2 | |
################################################################################## | |
# The following steps are for creating a new DO droplet, creating DNS records for it | |
# and copying the project files to it. | |
################################################################################## | |
- name: Install doctl | |
uses: digitalocean/action-doctl@v2 | |
with: | |
token: ${{ secrets.DO_ACCESS_TOKEN }} | |
- name: Create new droplet and DNS records | |
run: | | |
NEW_DROPLET_NAME=zubhub-test-${{ github.event.pull_request.number }} | |
FRONTEND_DOMAIN=${NEW_DROPLET_NAME} | |
API_DOMAIN=api.${FRONTEND_DOMAIN} | |
MEDIA_DOMAIN=media.${FRONTEND_DOMAIN} | |
echo "NEW_DROPLET_NAME=$NEW_DROPLET_NAME" >> $GITHUB_ENV | |
echo "FRONTEND_DOMAIN=$FRONTEND_DOMAIN" >> $GITHUB_ENV | |
echo "API_DOMAIN=$API_DOMAIN" >> $GITHUB_ENV | |
echo "MEDIA_DOMAIN=$MEDIA_DOMAIN" >> $GITHUB_ENV | |
# check if droplet already exists and exit script if it does | |
NEW_DROPLET_IP=$(doctl compute droplet get $NEW_DROPLET_NAME \ | |
--format PublicIPv4 --no-header 2>/dev/null || true) | |
if [ -n "$NEW_DROPLET_IP" ] ; then | |
echo "Droplet already exists. Save droplet IP to env variable and exit..." | |
echo "NEW_DROPLET_IP=$NEW_DROPLET_IP" >> $GITHUB_ENV | |
exit 0 | |
fi | |
# create new droplet | |
doctl compute droplet create $NEW_DROPLET_NAME --image \ | |
${{ secrets.ZUBHUB_TEST_SNAPSHOT_ID }} --tag-name zubhub-test --size s-1vcpu-1gb \ | |
--region nyc1 --enable-monitoring --ssh-keys ${{ secrets.DO_PUBLIC_SSHKEY_FP }} --wait | |
sleep 30s | |
NEW_DROPLET_IP=$(doctl compute droplet get $NEW_DROPLET_NAME \ | |
--format PublicIPv4 --no-header) | |
echo "NEW_DROPLET_IP=$NEW_DROPLET_IP" >> $GITHUB_ENV | |
# we only need records for frontend, media server and api server | |
doctl compute domain records create unstructured.studio --record-type A --record-name \ | |
$FRONTEND_DOMAIN --record-data $NEW_DROPLET_IP --record-ttl 60 | |
doctl compute domain records create unstructured.studio --record-type A --record-name \ | |
$API_DOMAIN --record-data $NEW_DROPLET_IP --record-ttl 60 | |
doctl compute domain records create unstructured.studio --record-type A --record-name \ | |
$MEDIA_DOMAIN --record-data $NEW_DROPLET_IP --record-ttl 60 | |
- name: Copy file via scp | |
uses: appleboy/scp-action@master | |
with: | |
host: ${{env.NEW_DROPLET_IP}} | |
username: ${{ secrets.DO_BACKEND_USERNAME }} | |
key: ${{ secrets.DO_SSHKEY }} | |
source: "." | |
target: "/home/zubhub" | |
- name: Set output | |
id: set_output | |
run: | | |
echo "::set-output name=NEW_DROPLET_IP::$NEW_DROPLET_IP" | |
echo "::set-output name=FRONTEND_DOMAIN::$FRONTEND_DOMAIN" | |
echo "::set-output name=API_DOMAIN::$API_DOMAIN" | |
echo "::set-output name=MEDIA_DOMAIN::$MEDIA_DOMAIN" | |
outputs: | |
NEW_DROPLET_IP: ${{ steps.set_output.outputs.NEW_DROPLET_IP }} | |
FRONTEND_DOMAIN: ${{ steps.set_output.outputs.FRONTEND_DOMAIN }} | |
API_DOMAIN: ${{ steps.set_output.outputs.API_DOMAIN }} | |
MEDIA_DOMAIN: ${{ steps.set_output.outputs.MEDIA_DOMAIN }} | |
################################################################################# | |
################################################################################# | |
# The following job is for building docker images and pushing them to dockerhub | |
################################################################################## | |
build_and_push: | |
needs: create_test_vm | |
runs-on: ubuntu-latest | |
strategy: | |
matrix: | |
service: ['frontend', 'web', 'celery', 'media'] | |
steps: | |
- name: Use output | |
id: use_output | |
run: | | |
echo "NEW_DROPLET_IP=${{ needs.create_test_vm.outputs.NEW_DROPLET_IP }}" >> $GITHUB_ENV | |
echo "FRONTEND_DOMAIN=${{ needs.create_test_vm.outputs.FRONTEND_DOMAIN }}" >> $GITHUB_ENV | |
echo "API_DOMAIN=${{ needs.create_test_vm.outputs.API_DOMAIN }}" >> $GITHUB_ENV | |
echo "MEDIA_DOMAIN=${{ needs.create_test_vm.outputs.MEDIA_DOMAIN }}" >> $GITHUB_ENV | |
- name: Checkout files | |
uses: actions/checkout@v2 | |
- name: create a .env file in the zubhub_frontend/zubhub/ directory | |
run: | | |
# create env file for frontend. | |
# This vm is only for testing so we don't need to worry about secrets being exposed | |
# (provided we don't put sensitive info in the env file) | |
cat << EOF > ./zubhub_frontend/zubhub/.env | |
GENERATE_SOURCEMAP=false | |
DISABLE_ESLINT_PLUGIN=true | |
REACT_APP_NODE_ENV=development | |
REACT_APP_BACKEND_DEVELOPMENT_URL=https://${{env.API_DOMAIN}}.unstructured.studio | |
REACT_APP_BACKEND_PRODUCTION_URL=https://${{env.API_DOMAIN}}.unstructured.studio | |
REACT_APP_DOSPACE_ACCESS_KEY_ID= | |
REACT_APP_DOSPACE_ACCESS_SECRET_KEY= | |
REACT_APP_VIDEO_UPLOAD_URL= | |
REACT_APP_VIDEO_FOLDER_NAME=videos | |
REACT_APP_DEV_VIDEO_FOLDER_NAME=dev_videos | |
REACT_APP_VIDEO_UPLOAD_PRESET_NAME=video_upload_preset | |
REACT_APP_DEV_VIDEO_UPLOAD_PRESET_NAME=dev_video_upload_preset | |
EOF | |
- name: Set up QEMU | |
uses: docker/setup-qemu-action@v1 | |
- name: Set up Docker Buildx | |
uses: docker/setup-buildx-action@v1 | |
- name: Login to DockerHub | |
uses: docker/login-action@v1 | |
with: | |
username: ${{ secrets.DOCKERHUB_USERNAME }} | |
password: ${{ secrets.DOCKERHUB_TOKEN }} | |
- name: Build and push ${{ matrix.service }} | |
id: docker_build | |
uses: docker/build-push-action@v2 | |
with: | |
# for frontend, context is ./zubhub_frontend/zubhub/ | |
# for backend services, context is ./zubhub_backend/ | |
context: ./zubhub_${{ matrix.service == 'frontend' && 'frontend/zubhub' || 'backend' }}/ | |
# for frontend, Dockerfile is ./zubhub_frontend/zubhub/Dockerfile.prod | |
# for backend services, Dockerfile is ./zubhub_backend/compose/<service>/prod/Dockerfile | |
file: ./zubhub_${{ matrix.service == 'frontend' && 'frontend/zubhub' || format('backend/compose/{0}', matrix.service) }}/${{ matrix.service != 'frontend' && 'prod/' || '' }}Dockerfile${{ matrix.service == 'frontend' && '.prod' || '' }} | |
push: true | |
tags: unstructuredstudio/zubhub-test_${{ matrix.service }}:latest | |
- name: Image digest | |
run: echo ${{ steps.docker_build.outputs.digest }} | |
################################################################################## | |
################################################################################# | |
# The following job is for deploying the project on the test droplet | |
################################################################################## | |
deploy: | |
needs: | |
- create_test_vm | |
- build_and_push | |
runs-on: ubuntu-latest | |
steps: | |
- name: Use output | |
id: use_output | |
run: | | |
echo "NEW_DROPLET_IP=${{ needs.create_test_vm.outputs.NEW_DROPLET_IP }}" >> $GITHUB_ENV | |
echo "FRONTEND_DOMAIN=${{ needs.create_test_vm.outputs.FRONTEND_DOMAIN }}" >> $GITHUB_ENV | |
echo "API_DOMAIN=${{ needs.create_test_vm.outputs.API_DOMAIN }}" >> $GITHUB_ENV | |
echo "MEDIA_DOMAIN=${{ needs.create_test_vm.outputs.MEDIA_DOMAIN }}" >> $GITHUB_ENV | |
- name: Executing remote command | |
uses: appleboy/ssh-action@master | |
with: | |
host: ${{env.NEW_DROPLET_IP}} | |
username: ${{ secrets.DO_BACKEND_USERNAME }} | |
key: ${{ secrets.DO_SSHKEY }} | |
script: | | |
# create env file for backend | |
cat << EOF > /home/zubhub_backend/.env | |
ENVIRONMENT=production | |
DEFAULT_FRONTEND_DOMAIN=${{env.FRONTEND_DOMAIN}}.unstructured.studio | |
DEFAULT_BACKEND_DOMAIN=${{env.API_DOMAIN}}.unstructured.studio | |
DEFAULT_DISPLAY_NAME=ZubHub | |
DEFAULT_FRONTEND_PROTOCOL=https | |
DEFAULT_BACKEND_PROTOCOL=https | |
SECRET_KEY=random string | |
DEBUG=1 | |
STORE_MEDIA_LOCALLY=1 | |
MEDIA_SECRET=random string | |
DEFAULT_MEDIA_SERVER_PROTOCOL=https | |
DEFAULT_MEDIA_SERVER_DOMAIN=${{env.MEDIA_DOMAIN}}.unstructured.studio | |
POSTGRES_NAME=postgres | |
POSTGRES_USER=postgres | |
POSTGRES_PASSWORD=postgres | |
POSTGRES_HOST=db | |
GF_ADMIN_USER=admin | |
GF_ADMIN_PASSWORD=admin | |
RABBITMQ_DEFAULT_USER=admin | |
RABBITMQ_DEFAULT_PASS=admin | |
CELERY_BROKER=amqp://admin:admin@rabbitmq:5672/ | |
CELERY_BACKEND=django-db | |
CELERY_FLOWER_USER=admin | |
CELERY_FLOWER_PASSWORD=admin | |
PROXY_COUNT=0 | |
DETECT_MISCONFIG=0 | |
SUPERUSER_PASSWORD=dummy_password | |
EOF | |
# deploy project | |
cd /home/zubhub | |
# Use sed to prepend "NODE_ENV=development" to the frontend build command. | |
# This is neccessary to avoid long build times on the test droplet. | |
sed -i '/"build":/ s/node /NODE_ENV=development node /' \ | |
/home/zubhub/zubhub_frontend/zubhub/package.json | |
sudo bash /home/zubhub/test_vm_deployment/deploy_test_fullstack.sh | |
###################################################################################### | |
################################################################################# | |
# The following job is for destroying the test droplet and its DNS records | |
################################################################################## | |
destroy_test_vm: | |
if: | | |
github.event.action == 'closed' || | |
(github.event.action == 'unlabeled' && github.event.label.name == 'create-test-vm') || | |
( | |
github.event.action == 'reopened' || | |
github.event.action == 'synchronize' | |
) && contains(github.event.pull_request.labels.*.name, 'create-test-vm') != true | |
runs-on: ubuntu-latest | |
steps: | |
- name: Install doctl | |
uses: digitalocean/action-doctl@v2 | |
with: | |
token: ${{ secrets.DO_ACCESS_TOKEN }} | |
- name: Get pr number and droplet ip | |
run: | | |
# for events like unlabelled, github.event.pull_request.number is available | |
PR_NUMBER=${{ github.event.pull_request.number }} | |
if [ -z "$PR_NUMBER" ] ; then | |
# github.event.pull_request.number is not available in closed event | |
sudo apt-get install jq -y | |
PR_NUMBER=$(curl --silent --show-error -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | |
https://api.github.com/repos/${{ github.repository }}/commits/${{ github.sha }}/pulls | \ | |
jq -r '.[0].number') | |
fi | |
if [ -z "$PR_NUMBER" ] ; then | |
echo "PR_NUMBER is not available. Exiting..." | |
exit 1 | |
fi | |
DROPLET_NAME=zubhub-test-$PR_NUMBER | |
# Get droplet ip | |
DROPLET_IP=$(doctl compute droplet get $DROPLET_NAME \ | |
--format PublicIPv4 --no-header 2>/dev/null || true) | |
if [ -z "$DROPLET_IP" ] ; then | |
echo "Droplet does not exist. Exiting..." | |
exit 0 | |
fi | |
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV | |
echo "DROPLET_IP=$DROPLET_IP" >> $GITHUB_ENV | |
echo "DROPLET_NAME=$DROPLET_NAME" >> $GITHUB_ENV | |
- name: delete DNS records | |
run: | | |
# Get the domain records | |
records=$(doctl compute domain records list unstructured.studio --format ID,Data --no-header) | |
# Loop through the records | |
while IFS= read -r record; do | |
# Split the record into ID and IP | |
ID=$(echo $record | cut -d' ' -f1) | |
IP=$(echo $record | cut -d' ' -f2) | |
# Delete the record if it points to the droplet IP | |
if [ -n "$ID" && "$IP" == "${{env.DROPLET_IP}}" ]; then | |
doctl compute domain records delete unstructured.studio $ID --force | |
fi | |
done <<< "$records" | |
- name: Delete test droplet | |
run: | | |
if [ -n "${{ env.DROPLET_NAME }}" ] ; then | |
doctl compute droplet delete ${{ env.DROPLET_NAME }} --force | |
fi | |
###################################################################################### |