From 0f2ab9ecde0beca5083118298e360638357bf797 Mon Sep 17 00:00:00 2001 From: carmen-chau <80921817+carmen-chau@users.noreply.github.com> Date: Sun, 10 Nov 2024 10:14:11 -0500 Subject: [PATCH 1/6] update init.py file --- .../hackathon_site/settings/__init__.py | 55 ++++++++++++++----- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/hackathon_site/hackathon_site/settings/__init__.py b/hackathon_site/hackathon_site/settings/__init__.py index 3d6d6b9..0f2b8a9 100644 --- a/hackathon_site/hackathon_site/settings/__init__.py +++ b/hackathon_site/hackathon_site/settings/__init__.py @@ -22,7 +22,6 @@ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = Path(__file__).resolve(strict=True).parent.parent.parent - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ @@ -44,11 +43,31 @@ HSS_URL = "http://localhost:3000/" else: # NOTE: If you aren't ieee uoft, put your websites here - ALLOWED_HOSTS = ["ieee.utoronto.ca"] + ALLOWED_HOSTS = [ + "hackstudentlife.ca", + "www.hackstudentlife.ca", + "hardware.hackstudentlife.ca", + "www.hardware.hackstudentlife.ca", + ] CORS_ORIGIN_REGEX_WHITELIST = [ - r"^https://ieee\.utoronto.ca:?\d*$", + r"^https://(?:www\.)?hackstudentlife\.ca", + r"^https://(?:www\.)?\w+\.hackstudentlife\.ca", + ] + HSS_URL = "https://hardware.hackstudentlife.ca/" + CSRF_COOKIE_DOMAIN = ".hackstudentlife.ca" + CSRF_TRUSTED_ORIGINS = [ + "hackstudentlife.ca", + "www.hackstudentlife.ca", + "hardware.hackstudentlife.ca", + "www.hardware.hackstudentlife.ca", ] - HSS_URL = "https://hardware.newhacks.ca/" + EMAIL_HOST = os.environ.get("EMAIL_HOST", None) + EMAIL_PORT = os.environ.get("EMAIL_PORT", None) + EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", None) + EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", None) + EMAIL_USE_SSL = True + DEFAULT_FROM_EMAIL = os.environ.get("EMAIL_FROM_ADDRESS", "hello@hackstudentlife.ca") + CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_CREDENTIALS = True @@ -92,6 +111,7 @@ "django_filters", "client_side_image_cropping", "captcha", + "qrcode", "dashboard", "registration", "event", @@ -146,6 +166,7 @@ LOGIN_REDIRECT_URL = reverse_lazy("event:dashboard") LOGOUT_REDIRECT_URL = reverse_lazy("event:index") +MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage" # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases @@ -158,6 +179,7 @@ "PASSWORD": os.environ.get("DB_PASSWORD", ""), "HOST": os.environ.get("DB_HOST", "127.0.0.1"), "PORT": os.environ.get("DB_PORT", "5432"), + "ATOMIC_REQUESTS": True, } } @@ -170,7 +192,7 @@ "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": f"redis://{REDIS_URI}", - "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient",}, + "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient", }, # Default time for cache key expiry, in seconds. Can be changed on a per-key basis "TIMEOUT": 600, } @@ -188,7 +210,6 @@ {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ] - REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework.authentication.TokenAuthentication", @@ -196,8 +217,8 @@ ], "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"], - "PAGE_SIZE": 100, - "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend",], + "PAGE_SIZE": 1000, + "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend", ], } # Internationalization @@ -249,8 +270,8 @@ "version": 1, "disable_existing_loggers": False, "filters": { - "require_debug_false": {"()": "django.utils.log.RequireDebugFalse",}, - "require_debug_true": {"()": "django.utils.log.RequireDebugTrue",}, + "require_debug_false": {"()": "django.utils.log.RequireDebugFalse", }, + "require_debug_true": {"()": "django.utils.log.RequireDebugTrue", }, }, "handlers": { "console": { @@ -289,11 +310,12 @@ } # Event specific settings -HACKATHON_NAME = "CoolHacks" -DEFAULT_FROM_EMAIL = "webmaster@localhost" +HACKATHON_NAME = "Hack The Student Life" +DEFAULT_FROM_EMAIL = "hello@hackstudentlife.ca" CONTACT_EMAIL = DEFAULT_FROM_EMAIL -HSS_ADMIN_EMAIL = "hardware@newhacks.ca" +HSS_ADMIN_EMAIL = "hardware@hackstudentlife.ca" +# TODO: CHANGE REGISTRATION_OPEN_DATE = datetime(2020, 9, 1, tzinfo=TZ_INFO) REGISTRATION_CLOSE_DATE = datetime(2023, 9, 30, tzinfo=TZ_INFO) EVENT_START_DATE = datetime(2023, 10, 10, 10, 0, 0, tzinfo=TZ_INFO) @@ -302,13 +324,14 @@ HARDWARE_SIGN_OUT_END_DATE = datetime(2024, 9, 30, tzinfo=TZ_INFO) # Registration user requirements -MINIMUM_AGE = 14 +MINIMUM_AGE = 18 # Registration settings ACCOUNT_ACTIVATION_DAYS = 7 RSVP_DAYS = 7 # Team requirements +# TODO: Double check MIN_MEMBERS = 2 MAX_MEMBERS = 4 @@ -321,10 +344,14 @@ FINAL_REVIEW_RESPONSE_DATE = REGISTRATION_CLOSE_DATE + timedelta(days=7) # Links + +# TODO: CHANGE PARTICIPANT_PACKAGE_LINK = "#" # Note this is in the form (chat_room_name, chat_room_link) # Chat room name is such as the following: Slack, Discord + +# TODO: CHANGE CHAT_ROOM = ("Slack", "https://slack.com") # Enable/Disable certain Features From ee7a6a9d8e3b5a4f990b30c91fa7c4af83c49d57 Mon Sep 17 00:00:00 2001 From: carmen-chau <80921817+carmen-chau@users.noreply.github.com> Date: Sun, 10 Nov 2024 10:16:55 -0500 Subject: [PATCH 2/6] black changes --- hackathon_site/hackathon_site/settings/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/hackathon_site/hackathon_site/settings/__init__.py b/hackathon_site/hackathon_site/settings/__init__.py index 0f2b8a9..b6807c9 100644 --- a/hackathon_site/hackathon_site/settings/__init__.py +++ b/hackathon_site/hackathon_site/settings/__init__.py @@ -66,7 +66,9 @@ EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", None) EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", None) EMAIL_USE_SSL = True - DEFAULT_FROM_EMAIL = os.environ.get("EMAIL_FROM_ADDRESS", "hello@hackstudentlife.ca") + DEFAULT_FROM_EMAIL = os.environ.get( + "EMAIL_FROM_ADDRESS", "hello@hackstudentlife.ca" + ) CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_CREDENTIALS = True @@ -192,7 +194,7 @@ "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": f"redis://{REDIS_URI}", - "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient", }, + "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient",}, # Default time for cache key expiry, in seconds. Can be changed on a per-key basis "TIMEOUT": 600, } @@ -218,7 +220,7 @@ "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"], "PAGE_SIZE": 1000, - "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend", ], + "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend",], } # Internationalization @@ -270,8 +272,8 @@ "version": 1, "disable_existing_loggers": False, "filters": { - "require_debug_false": {"()": "django.utils.log.RequireDebugFalse", }, - "require_debug_true": {"()": "django.utils.log.RequireDebugTrue", }, + "require_debug_false": {"()": "django.utils.log.RequireDebugFalse",}, + "require_debug_true": {"()": "django.utils.log.RequireDebugTrue",}, }, "handlers": { "console": { From 8bab2eb5a314fc9eda897ffab6e9cc0ec7aa2265 Mon Sep 17 00:00:00 2001 From: carmen-chau <80921817+carmen-chau@users.noreply.github.com> Date: Sun, 10 Nov 2024 10:21:27 -0500 Subject: [PATCH 3/6] update requirements.txt with the qr code dependency --- hackathon_site/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/hackathon_site/requirements.txt b/hackathon_site/requirements.txt index d499015..f3ad7eb 100644 --- a/hackathon_site/requirements.txt +++ b/hackathon_site/requirements.txt @@ -32,6 +32,7 @@ python-dateutil==2.8.2 pyparsing==2.4.7 pytz==2020.1 PyYAML==5.4 +qrcode==7.3.1 redis==3.5.3 regex==2021.4.4 requests==2.25.1 From 99aa09a620979f1db3fc9a0120aee2d21ea318c6 Mon Sep 17 00:00:00 2001 From: carmen-chau <80921817+carmen-chau@users.noreply.github.com> Date: Sun, 10 Nov 2024 10:38:13 -0500 Subject: [PATCH 4/6] added docker files --- deployment/Dockerfile | 4 + deployment/docker-compose.ci.yml | 8 ++ deployment/docker-compose.prod.yml | 37 ++++++++ deployment/docker-stack-wait.sh | 148 +++++++++++++++++++++++++++++ deployment/entrypoint.sh | 18 +++- 5 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 deployment/docker-compose.ci.yml create mode 100644 deployment/docker-compose.prod.yml create mode 100644 deployment/docker-stack-wait.sh diff --git a/deployment/Dockerfile b/deployment/Dockerfile index ce5eedc..def1217 100644 --- a/deployment/Dockerfile +++ b/deployment/Dockerfile @@ -7,6 +7,10 @@ WORKDIR /usr/src/hackathon_site ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 +# Set time zone +RUN cp /usr/share/zoneinfo/Canada/Eastern /etc/localtime \ + && echo "America/Toronto" > /etc/timezone + # Install dependencies RUN apt-get update && apt-get install -y postgresql-client diff --git a/deployment/docker-compose.ci.yml b/deployment/docker-compose.ci.yml new file mode 100644 index 0000000..ff0a07a --- /dev/null +++ b/deployment/docker-compose.ci.yml @@ -0,0 +1,8 @@ +version: "3.8" + +services: + django: + build: + context: .. + dockerfile: ./deployment/Dockerfile + image: ${REGISTRY}/${IMAGE_NAME}/django:${GITHUB_SHA_SHORT} diff --git a/deployment/docker-compose.prod.yml b/deployment/docker-compose.prod.yml new file mode 100644 index 0000000..ff0e280 --- /dev/null +++ b/deployment/docker-compose.prod.yml @@ -0,0 +1,37 @@ +version: "3.8" + +services: + django: + image: ${REGISTRY}/${IMAGE_NAME}/django:${GITHUB_SHA_SHORT} + command: gunicorn hackathon_site.wsgi:application --bind 0.0.0.0:8000 --workers 5 --capture-output --access-logfile - --error-logfile - + ports: + - "8000:8000" + env_file: .env + volumes: + - /var/www/${IMAGE_NAME}/media/:/var/www/media/ + deploy: + replicas: 1 + update_config: + failure_action: rollback + order: start-first + restart_policy: + condition: on-failure + networks: + - aws-hacks-2024 + redis: + image: redis:6-alpine + ports: + - "6379:6379" + deploy: + replicas: 1 + update_config: + failure_action: rollback + order: start-first + restart_policy: + condition: on-failure + networks: + - aws-hacks-2024 + +networks: + aws-hacks-2024: + driver: overlay diff --git a/deployment/docker-stack-wait.sh b/deployment/docker-stack-wait.sh new file mode 100644 index 0000000..c7f00a3 --- /dev/null +++ b/deployment/docker-stack-wait.sh @@ -0,0 +1,148 @@ +#!/bin/sh + +# By: Brandon Mitchell +# License: MIT +# Source repo: https://github.com/sudo-bmitch/docker-stack-wait + +set -e +trap "{ exit 1; }" TERM INT +opt_h=0 +opt_r=0 +opt_s=5 +opt_t=3600 +start_epoc=$(date +%s) + +usage() { + echo "$(basename $0) [opts] stack_name" + echo " -f filter: only wait for services matching filter, may be passed multiple" + echo " times, see docker stack services for the filter syntax" + echo " -h: this help message" + echo " -n name: only wait for specific service names, overrides any filters," + echo " may be passed multiple times, do not include the stack name prefix" + echo " -r: treat a rollback as successful" + echo " -s sec: frequency to poll service state (default $opt_s sec)" + echo " -t sec: timeout to stop waiting" + [ "$opt_h" = "1" ] && exit 0 || exit 1 +} +check_timeout() { + # timeout when a timeout is defined and we will exceed the timeout after the + # next sleep completes + if [ "$opt_t" -gt 0 ]; then + cur_epoc=$(date +%s) + cutoff_epoc=$(expr ${start_epoc} + $opt_t - $opt_s) + if [ "$cur_epoc" -gt "$cutoff_epoc" ]; then + echo "Error: Timeout exceeded" + exit 1 + fi + fi +} +get_service_ids() { + if [ -n "$opt_n" ]; then + service_list="" + for name in $opt_n; do + service_list="${service_list:+${service_list} }${stack_name}_${name}" + done + docker service inspect --format '{{.ID}}' ${service_list} + else + docker stack services ${opt_f} -q "${stack_name}" + fi +} +service_state() { + # output the state when it changes from the last state for the service + service=$1 + # strip any invalid chars from service name for caching state + service_safe=$(echo "$service" | sed 's/[^A-Za-z0-9_]/_/g') + state=$2 + if eval [ \"\$cache_${service_safe}\" != \"\$state\" ]; then + echo "Service $service state: $state" + eval cache_${service_safe}=\"\$state\" + fi +} + +while getopts 'f:hn:rs:t:' opt; do + case $opt in + f) opt_f="${opt_f:+${opt_f} }-f $OPTARG";; + h) opt_h=1;; + n) opt_n="${opt_n:+${opt_n} } $OPTARG";; + r) opt_r=1;; + s) opt_s="$OPTARG";; + t) opt_t="$OPTARG";; + esac +done +shift $(expr $OPTIND - 1) + +if [ $# -ne 1 -o "$opt_h" = "1" -o "$opt_s" -le "0" ]; then + usage +fi + +stack_name=$1 + +# 0 = running, 1 = success, 2 = error +stack_done=0 +while [ "$stack_done" != "1" ]; do + stack_done=1 + # run get_service_ids outside of the for loop to catch errors + service_ids=$(get_service_ids) + for service_id in ${service_ids}; do + service_done=1 + service=$(docker service inspect --format '{{.Spec.Name}}' "$service_id") + + # hardcode a "new" state when UpdateStatus is not defined + state=$(docker service inspect -f '{{if .UpdateStatus}}{{.UpdateStatus.State}}{{else}}new{{end}}' "$service_id") + + # check for failed update states + case "$state" in + paused|rollback_paused) + service_done=2 + ;; + rollback_*) + if [ "$opt_r" = "0" ]; then + service_done=2 + fi + ;; + esac + + # identify/report current state + if [ "$service_done" != "2" ]; then + replicas=$(docker service ls --format '{{.Replicas}}' --filter "id=$service_id" | cut -d' ' -f1) + current=$(echo "$replicas" | cut -d/ -f1) + target=$(echo "$replicas" | cut -d/ -f2) + if [ "$current" != "$target" ]; then + # actively replicating service + service_done=0 + state="replicating $replicas" + fi + fi + service_state "$service" "$state" + + # check for states that indicate an update is done + if [ "$service_done" = "1" ]; then + case "$state" in + new|completed|rollback_completed) + service_done=1 + ;; + *) + # any other state is unknown, not necessarily finished + service_done=0 + ;; + esac + fi + + # update stack done state + if [ "$service_done" = "2" ]; then + # error condition + stack_done=2 + elif [ "$service_done" = "0" -a "$stack_done" = "1" ]; then + # only go to an updating state if not in an error state + stack_done=0 + fi + done + if [ "$stack_done" = "2" ]; then + echo "Error: This deployment will not complete" + exit 1 + fi + if [ "$stack_done" != "1" ]; then + check_timeout + sleep "${opt_s}" + fi +done diff --git a/deployment/entrypoint.sh b/deployment/entrypoint.sh index 581d8d5..704de48 100644 --- a/deployment/entrypoint.sh +++ b/deployment/entrypoint.sh @@ -2,10 +2,20 @@ echo "Waiting for the database..." -while ! pg_isready -d $DB_NAME -h $DB_HOST -p $DB_PORT -U $DB_USER; do +TIMEOUT=120 +ATTEMPT=0 +command="pg_isready -d $DB_NAME -h $DB_HOST -p $DB_PORT -U $DB_USER" + +until [ "$ATTEMPT" -eq "$TIMEOUT" ] || eval $command; do + (( ATTEMPT++ )) sleep 1 done -echo "Database connection made" - -exec "$@" +if [ "$ATTEMPT" -lt "$TIMEOUT" ] +then + echo "Database connection made" + exec "$@" +else + echo "Failed to connect to database" + exit 1 +fi From b23e37095125d4df71ddf70c0d50bb7f1bcfe496 Mon Sep 17 00:00:00 2001 From: carmen-chau <80921817+carmen-chau@users.noreply.github.com> Date: Sun, 10 Nov 2024 10:42:20 -0500 Subject: [PATCH 5/6] deleted main.yml --- .github/workflows/main.yml | 82 -------------------------------------- 1 file changed, 82 deletions(-) delete mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index e853a21..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: CI/CD - -on: - pull_request_target: - branches: - - '**' - -jobs: - backend-checks: - runs-on: ubuntu-latest - defaults: - run: - working-directory: hackathon_site - - steps: - - uses: actions/checkout@v3 - with: - ref: ${{github.event.pull_request.head.ref}} - repository: ${{github.event.pull_request.head.repo.full_name}} - - name: Set up Python 3.8 - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Check formatting with Black - run: | - # Stop the build if there are any formatting issues picked up by Black - black --check . - - name: Tests - env: - SECRET_KEY: ${{ secrets.DJANGO_SECRET_KEY }} - DEBUG: 0 - run: python manage.py test --settings=hackathon_site.settings.ci - - template-checks: - runs-on: ubuntu-latest - defaults: - run: - working-directory: hackathon_site - - steps: - - uses: actions/checkout@v3 - with: - ref: ${{github.event.pull_request.head.ref}} - repository: ${{github.event.pull_request.head.repo.full_name}} - - name: Use Node.js 16.x - uses: actions/setup-node@v3 - with: - node-version: '16.x' - - name: Install dependencies - run: yarn install - - name: Formatting check - run: yarn run prettier-check - - dashboard-checks: - runs-on: ubuntu-latest - defaults: - run: - working-directory: hackathon_site/dashboard/frontend - - steps: - - uses: actions/checkout@v3 - with: - ref: ${{github.event.pull_request.head.ref}} - repository: ${{github.event.pull_request.head.repo.full_name}} - - name: Use Node.js 16.x - uses: actions/setup-node@v3 - with: - node-version: '16.x' - - name: Install dependencies - run: yarn install - - name: Formatting check - run: yarn prettier --check 'src/**/*.(js|ts|tsx|scss)' - - name: Typescript check - run: yarn run tsc - - name: Tests - run: yarn test --watchAll=false - - name: Build frontend - run: yarn run build From 44c6f4f6fd18780ba2e96422366edbbbbf9dd447 Mon Sep 17 00:00:00 2001 From: carmen-chau <80921817+carmen-chau@users.noreply.github.com> Date: Sun, 10 Nov 2024 11:50:40 -0500 Subject: [PATCH 6/6] added workflow yml files --- .github/workflows/checks.yml | 78 +++++++++++++++++++ .github/workflows/deploy.yml | 144 +++++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 .github/workflows/checks.yml create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..cbdddd5 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,78 @@ +name: Checks + +on: + pull_request_target: + branches: + - '**' + +jobs: + backend-checks: + runs-on: ubuntu-latest + defaults: + run: + working-directory: hackathon_site + + steps: + - uses: actions/checkout@v3 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + - name: Set up Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Check formatting with Black + run: | + # Stop the build if there are any formatting issues picked up by Black + black --check . + - name: Tests + env: + SECRET_KEY: ${{ secrets.CI_SECRET_KEY }} + DEBUG: 0 + run: python manage.py test --settings=hackathon_site.settings.ci + + template-checks: + runs-on: ubuntu-latest + defaults: + run: + working-directory: hackathon_site + + steps: + - uses: actions/checkout@v3 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + - name: Use Node.js 16.x + uses: actions/setup-node@v3 + with: + node-version: '16.x' + - name: Install dependencies + run: yarn install + + dashboard-checks: + runs-on: ubuntu-latest + defaults: + run: + working-directory: hackathon_site/dashboard/frontend + + steps: + - uses: actions/checkout@v3 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + - name: Use Node.js 16.x + uses: actions/setup-node@v3 + with: + node-version: '16.x' + - name: Install dependencies + run: yarn install + - name: Formatting check + run: yarn prettier --check 'src/**/*.(js|ts|tsx|scss)' + - name: Typescript check + run: yarn run tsc + - name: Tests + run: yarn test --watchAll=false diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..2daf3c8 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,144 @@ +name: Deploy + +on: + push: + branches: + - 'develop' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + STACK_NAME: ${{ github.repository }} + +jobs: + build: + runs-on: ubuntu-latest + outputs: + GITHUB_SHA_SHORT: ${{ steps.sha7.outputs.GITHUB_SHA_SHORT }} + + steps: + - uses: actions/checkout@v2 + - name: Get short SHA + id: sha7 + run: | + GITHUB_SHA_SHORT=$(echo ${{ github.sha }} | cut -c1-7) + echo "GITHUB_SHA_SHORT=${GITHUB_SHA_SHORT}" >> $GITHUB_ENV + echo "::set-output name=GITHUB_SHA_SHORT::${GITHUB_SHA_SHORT}" + - name: Build image + run: docker compose -f deployment/docker-compose.ci.yml build + - name: Docker login + uses: docker/login-action@v1.10.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Push image + run: docker compose -f deployment/docker-compose.ci.yml push + + deploy: + runs-on: ubuntu-latest + needs: [ build ] + environment: + name: production + url: https://hackstudentlife.ca + defaults: + run: + working-directory: deployment + env: + GITHUB_SHA_SHORT: ${{ needs.build.outputs.GITHUB_SHA_SHORT }} + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install python dependencies + working-directory: hackathon_site + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Use Node.js 16.x + uses: actions/setup-node@v3 + with: + node-version: '16.x' + - name: Install nodejs dependencies + working-directory: hackathon_site/event + run: yarn install + - name: Compile scss + working-directory: hackathon_site/event + run: yarn scss + - name: Collect static + working-directory: hackathon_site + env: + SECRET_KEY: ${{ secrets.CI_SECRET_KEY }} + run: python manage.py collectstatic + - name: Build frontend + working-directory: hackathon_site/dashboard/frontend + run: | + yarn install + yarn run build + - name: Set environment variables in .env + run: | + echo 'DEBUG=0' >> .env + echo 'SECRET_KEY=${{ secrets.CI_SECRET_KEY }}' >> .env + echo 'DB_NAME=${{ secrets.DB_NAME }}' >> .env + echo 'DB_USER=${{ secrets.DB_USER }}' >> .env + echo 'DB_PASSWORD=${{ secrets.DB_PASSWORD }}' >> .env + echo 'DB_HOST=${{ secrets.DB_HOST }}' >> .env + echo 'DB_PORT=${{ secrets.DB_PORT }}' >> .env + echo 'EMAIL_HOST=${{ secrets.EMAIL_HOST }}' >> .env + echo 'EMAIL_PORT=${{ secrets.EMAIL_PORT }}' >> .env + echo 'EMAIL_HOST_USER=${{ secrets.EMAIL_HOST_USER }}' >> .env + echo 'EMAIL_HOST_PASSWORD=${{ secrets.EMAIL_HOST_PASSWORD }}' >> .env + echo 'EMAIL_FROM_ADDRESS=${{ secrets.EMAIL_FROM_ADDRESS }}' >> .env + echo 'REDIS_URI=${{ secrets.REDIS_URI }}' >> .env + echo 'RECAPTCHA_PUBLIC_KEY=${{ secrets.RECAPTCHA_PUBLIC_KEY }}' >> .env + echo 'RECAPTCHA_PRIVATE_KEY=${{ secrets.RECAPTCHA_PRIVATE_KEY }}' >> .env + - name: Transfer static files to the Swarm manager + uses: appleboy/scp-action@v0.1.1 + with: + host: ${{ secrets.SWARM_MANAGER_IP }} + username: ${{ secrets.SSH_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + overwrite: true + # scp-action doesn't obey working-directory, runs at repo root + source: "hackathon_site/static/" + target: "/usr/src/${{ env.IMAGE_NAME }}" + strip_components: 1 + - name: Set up SSH + run: | + mkdir -p ~/.ssh + ssh-keyscan -t ed25519 ${{ secrets.SWARM_MANAGER_IP }} >> ~/.ssh/known_hosts + echo "${{ secrets.SSH_PRIVATE_KEY }}" >> ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + - name: Transfer frontend files to server + uses: appleboy/scp-action@v0.1.1 + with: + host: ${{ secrets.SWARM_MANAGER_IP }} + username: ${{ secrets.SSH_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + overwrite: true + # scp-action doesn't obey working-directory, runs at repo root + source: "hackathon_site/dashboard/frontend/build/" + target: "/usr/src/${{ env.IMAGE_NAME }}" + strip_components: 3 + - name: Bring up deployment + env: + DOCKER_HOST: ssh://${{ secrets.SSH_USER }}@${{ secrets.SWARM_MANAGER_IP }} + run: | + echo "Logging in to GitHub packages..." + echo ${{ secrets.GITHUB_TOKEN }} | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin + echo "Bringing up deployment..." + docker stack deploy --prune --with-registry-auth -c docker-compose.prod.yml ${{ env.STACK_NAME }} + echo "Waiting for deployment..." + sleep 30 + chmod 777 docker-stack-wait.sh + ./docker-stack-wait.sh -t 600 ${{ env.STACK_NAME }} + echo "Running migrations..." + # TODO: It would be better to use docker-compose against the django service, + # but there is currently a bug in docker-compose preventing running services + # over an SSH host. + IMAGE=${REGISTRY}/${IMAGE_NAME}/django:${GITHUB_SHA_SHORT} + docker run --rm --env-file .env ${IMAGE} python manage.py migrate + echo "Deployment complete"