From 83eda0d835c06db14a6f99d5043361a44ccae824 Mon Sep 17 00:00:00 2001 From: Jim Laney Date: Wed, 15 Jan 2025 12:01:35 -0800 Subject: [PATCH 1/4] use nginx to handle redirects --- Dockerfile | 27 +------ MANIFEST.in | 3 - docker-compose.yml | 14 +--- docker/prod-values.yml | 179 +++++++++++++---------------------------- requirements.txt | 1 - setup.py | 46 ----------- 6 files changed, 61 insertions(+), 209 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/Dockerfile b/Dockerfile index 96add1b9..05dd2a3a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,26 +1 @@ -ARG DJANGO_CONTAINER_VERSION=2.0.3 - -FROM us-docker.pkg.dev/uwit-mci-axdd/containers/django-container:${DJANGO_CONTAINER_VERSION} AS app-container - -USER root -RUN apt-get update && apt-get install libpq-dev -y -USER acait - -ADD --chown=acait:acait . /app/ -ADD --chown=acait:acait docker/ /app/project/ -ADD --chown=acait:acait docker/app_start.sh /scripts -RUN chmod u+x /scripts/app_start.sh - -RUN /app/bin/pip install -r requirements.txt -RUN /app/bin/pip install psycopg2 - -RUN . /app/bin/activate && pip install nodeenv && nodeenv -p &&\ - npm install -g npm && ./bin/npm install less -g - -RUN . /app/bin/activate && python manage.py collectstatic --noinput &&\ - python manage.py compress -f - -FROM us-docker.pkg.dev/uwit-mci-axdd/containers/django-test-container:${DJANGO_CONTAINER_VERSION} AS app-test-container - -COPY --from=app-container /app/ /app/ -COPY --from=app-container /static/ /static/ +FROM us-docker.pkg.dev/uwit-mci-axdd/containers/nginx-container:1.1.2 AS app-container diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 43184c56..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include README.md -recursive-include mdot/static * -recursive-include mdot/templates * diff --git a/docker-compose.yml b/docker-compose.yml index ecd09c61..b48404d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,20 +2,12 @@ version: '3.4' services: app: + environment: + PORT: 8000 restart: always container_name: mdot build: context: . - dockerfile: ${DOCKERFILE:-Dockerfile} target: app-container - # map the local (host) directories to their container counterparts - # to support live-syncing - volumes: - - ./mdot:/app/mdot ports: - - "${PORT:-8000}:8000" - environment: - ENV: localdev - AUTH: ${AUTH:-SAML_MOCK} - RESTCLIENTS_MDOT_DAO_CLASS: ${RESTCLIENTS_MDOT_DAO_CLASS:-Mock} - RESTCLIENTS_MDOT_HOST: ${RESTCLIENTS_MDOT_HOST:-None} + - "${RUNSERVER_PORT:-8000}:8000" diff --git a/docker/prod-values.yml b/docker/prod-values.yml index e95fa40e..51a9aca5 100644 --- a/docker/prod-values.yml +++ b/docker/prod-values.yml @@ -1,8 +1,8 @@ autoscaling: enabled: true - minReplicas: 2 - maxReplicas: 4 -targetAverageUtilization: 100 + minReplicas: 1 + maxReplicas: 2 + ingress: enabled: true tls: @@ -49,131 +49,66 @@ ingress: - "/" annotations: cert-manager.io/cluster-issuer: letsencrypt - nginx.ingress.kubernetes.io/ssl-ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256" - nginx.ingress.kubernetes.io/client-body-buffer-size: "16K" nginx.ingress.kubernetes.io/configuration-snippet: | return 301 https://www.washington.edu/mobile/; -lifecycle: - enabled: true - preStop: - enabled: true -affinity: - podsSpanNodes: true -readiness: - enabled: true -externalService: - enabled: true - name: mdot-db-service - type: ClusterIP - serviceAddress: 172.18.1.206 - servicePort: 5432 -database: - engine: postgres - name: mdot_prod - hostname: mdot-db-service - secretName: prod.mdot.uw.edu-sql-secrets + repo: mdot instance: prod -memcached: + +resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 25m + memory: 128Mi + +service: + enabled: true + ports: + - port: 80 + targetPort: 8000 + name: http + - port: 9113 + targetPort: 9113 + name: metrics + +deploymentInitialization: + enabled: false + +daemon: enabled: false + +database: + engine: null + +externalService: + enabled: false + cronjob: enabled: false + +memcached: + enabled: false + certs: - mounted: true - certPath: /certs/prod.mdot.uw.edu-uwca.cert - keyPath: /certs/prod.mdot.uw.edu-uwca.key - secretName: prod.mdot.uw.edu-uwca-certs -environmentVariables: - - name: AUTH - value: SAML - - name: CLUSTER_CNAME - value: mobile.uw.edu - - name: ENV - value: prod - - name: RESTCLIENTS_MDOT_DAO_CLASS - value: Live - - name: RESTCLIENTS_MDOT_HOST - value: https://prod-api.mdot.uw.edu - - name: SAML_ENTITY_ID - value: https://mobile.uw.edu/saml2 -externalSecrets: + mounted: false + +gcsCredentials: + mounted: false + +metrics: enabled: true - secrets: - - name: prod.mdot.uw.edu-secrets - externalKey: mdot/prod/secrets - data: - - name: admin-authz-group - property: admin-authz-group - - name: django-secret - property: django-secret - - name: google-analytics-key - property: google-analytics-key - - name: email-host - property: email-host - - name: mdot-help-email - property: mdot-help-email - - name: mdot-service-team-email - property: mdot-service-team-email - - name: mdot-ux-contact - property: mdot-ux-contact - - name: prod.mdot.uw.edu-sql-secrets - externalKey: mdot/prod/sql-secrets - data: - - name: username - property: username - - name: password - property: password - - name: prod.mdot.uw.edu-uwca-certs - externalKey: mdot/prod/uwca-certs - data: - - name: prod.mdot.uw.edu-uwca.cert - property: prod.mdot.uw.edu-uwca.cert - - name: prod.mdot.uw.edu-uwca.key - property: prod.mdot.uw.edu-uwca.key - - name: prod.mdot.uw.edu-ic-cert - externalKey: mdot/prod/ic-certs - data: - - name: prod.mdot.uw.edu-ic.cert - property: prod.mdot.uw.edu-ic.cert - - name: prod.mdot.uw.edu-uw-idp-cert - externalKey: idp-cert - data: - - name: uw-idp-cert - property: cert -environmentVariablesSecrets: - adminAuthzGroup: - name: ADMIN_AUTHZ_GROUP - secretName: prod.mdot.uw.edu-secrets - secretKey: admin-authz-group - djangoSecret: - name: DJANGO_SECRET - secretName: prod.mdot.uw.edu-secrets - secretKey: django-secret - googleAnalyticsKey: - name: GOOGLE_ANALYTICS_KEY - secretName: prod.mdot.uw.edu-secrets - secretKey: google-analytics-key - emailHost: - name: EMAIL_HOST - secretName: prod.mdot.uw.edu-secrets - secretKey: email-host - mdotHelpEmail: - name: MDOT_HELP_EMAIL - secretName: prod.mdot.uw.edu-secrets - secretKey: mdot-help-email - mdotUXContact: - name: MDOT_UX_CONTACT - secretName: prod.mdot.uw.edu-secrets - secretKey: mdot-ux-contact - mdotServiceEmail: - name: MDOT_SERVICE_EMAIL - secretName: prod.mdot.uw.edu-secrets - secretKey: mdot-service-team-email - samlSPCert: - name: SP_CERT - secretName: prod.mdot.uw.edu-ic-cert - secretKey: prod.mdot.uw.edu-ic.cert - samlIDPCert: - name: IDP_CERT - secretName: prod.mdot.uw.edu-uw-idp-cert - secretKey: uw-idp-cert + serviceMonitor: + enabled: true + port: metrics + +readiness: + enabled: false + +lifecycle: + enabled: true + +environmentVariables: + - name: "ENV" + value: "prod" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index d6e1198b..00000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e . diff --git a/setup.py b/setup.py deleted file mode 100644 index fab0612a..00000000 --- a/setup.py +++ /dev/null @@ -1,46 +0,0 @@ -import os -from setuptools import find_packages, setup - -README = open(os.path.join(os.path.dirname(__file__), 'README.md')).read() - -# allow setup.py to be run from any path -os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) - -setup( - name='mdot', - version='0.2', - packages=find_packages(), - include_package_data=True, - install_requires=[ - 'setuptools', - 'django~=4.2', - 'django-compressor', - 'UW-RestClients-Core~=1.4', - 'django-htmlmin', - 'lesscpy', - 'django-pyscss', - 'pyyaml', - 'ua-parser', - 'user-agents', - 'django-user-agents', - 'UW-Django-SAML2', - 'lxml==4.9.4', - 'xmlsec==1.3.13' - ], - license='Apache License, Version 2.0', - description='Mobile UW web application', - long_description=README, - url='https://github.com/uw-it-aca/mdot', - author='UW-IT T&LS', - author_email='aca-it@uw.edu', - classifiers=[ - 'Environment :: Web Environment', - 'Framework :: Django', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', - ], -) From 8dc6de657a0e724036c7830392dfa6443f73446a Mon Sep 17 00:00:00 2001 From: Jim Laney Date: Wed, 15 Jan 2025 12:06:59 -0800 Subject: [PATCH 2/4] update copyright --- .github/workflows/cicd.yml | 2 +- mdot/admin.py | 2 +- mdot/context_processors.py | 2 +- mdot/management/commands/timedtests.py | 2 +- mdot/mdot_rest_client/client.py | 2 +- mdot/models.py | 2 +- mdot/templatetags/mdot_extras.py | 2 +- mdot/test/admin_view.py | 2 +- mdot/test/client_error_catching.py | 2 +- mdot/test/client_get_resources.py | 2 +- mdot/test/client_helper_methods.py | 2 +- mdot/test/forms.py | 2 +- mdot/test/request_view.py | 2 +- mdot/test/url_and_view.py | 2 +- mdot/tests.py | 2 +- mdot/urls.py | 2 +- mdot/views.py | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 9da55058..bd1a2f6c 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -154,7 +154,7 @@ jobs: - name: Deployment Pipeline if: >- - contains(fromJSON('["main", "master", "develop", "qa"]'), + contains(fromJSON('["main"]'), needs.context.outputs.git_repo_branch) uses: uw-it-aca/actions/cicd-deploy@main with: diff --git a/mdot/admin.py b/mdot/admin.py index 3bcaf4db..dbcc4320 100644 --- a/mdot/admin.py +++ b/mdot/admin.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.conf import settings diff --git a/mdot/context_processors.py b/mdot/context_processors.py index f11a9f05..02616d3b 100644 --- a/mdot/context_processors.py +++ b/mdot/context_processors.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.conf import settings diff --git a/mdot/management/commands/timedtests.py b/mdot/management/commands/timedtests.py index c2cf7e4c..55c45b65 100644 --- a/mdot/management/commands/timedtests.py +++ b/mdot/management/commands/timedtests.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from datetime import datetime diff --git a/mdot/mdot_rest_client/client.py b/mdot/mdot_rest_client/client.py index e1696fdd..47f90ef3 100644 --- a/mdot/mdot_rest_client/client.py +++ b/mdot/mdot_rest_client/client.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from commonconf import settings diff --git a/mdot/models.py b/mdot/models.py index 8fae09f3..170e5a58 100644 --- a/mdot/models.py +++ b/mdot/models.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from datetime import timedelta, date, datetime diff --git a/mdot/templatetags/mdot_extras.py b/mdot/templatetags/mdot_extras.py index d36e251d..67c89c64 100644 --- a/mdot/templatetags/mdot_extras.py +++ b/mdot/templatetags/mdot_extras.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django import template diff --git a/mdot/test/admin_view.py b/mdot/test/admin_view.py index 21d991f5..47fc7481 100644 --- a/mdot/test/admin_view.py +++ b/mdot/test/admin_view.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import unittest diff --git a/mdot/test/client_error_catching.py b/mdot/test/client_error_catching.py index a71d4d02..2618c055 100644 --- a/mdot/test/client_error_catching.py +++ b/mdot/test/client_error_catching.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase diff --git a/mdot/test/client_get_resources.py b/mdot/test/client_get_resources.py index 72fdec92..4165c00f 100644 --- a/mdot/test/client_get_resources.py +++ b/mdot/test/client_get_resources.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase, override_settings diff --git a/mdot/test/client_helper_methods.py b/mdot/test/client_helper_methods.py index 9c816ba2..d8305e11 100644 --- a/mdot/test/client_helper_methods.py +++ b/mdot/test/client_helper_methods.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase, Client diff --git a/mdot/test/forms.py b/mdot/test/forms.py index 018372ae..e7760519 100755 --- a/mdot/test/forms.py +++ b/mdot/test/forms.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import Client, TestCase diff --git a/mdot/test/request_view.py b/mdot/test/request_view.py index 5d8ccbfe..b46cc9a7 100644 --- a/mdot/test/request_view.py +++ b/mdot/test/request_view.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import Client, TestCase diff --git a/mdot/test/url_and_view.py b/mdot/test/url_and_view.py index 5369476a..3f386dcd 100644 --- a/mdot/test/url_and_view.py +++ b/mdot/test/url_and_view.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import Client, TestCase, override_settings diff --git a/mdot/tests.py b/mdot/tests.py index c46da92f..41a0eabb 100644 --- a/mdot/tests.py +++ b/mdot/tests.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ diff --git a/mdot/urls.py b/mdot/urls.py index 318965b4..0388b93f 100644 --- a/mdot/urls.py +++ b/mdot/urls.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.urls import path diff --git a/mdot/views.py b/mdot/views.py index 9536742f..52c43af1 100644 --- a/mdot/views.py +++ b/mdot/views.py @@ -1,4 +1,4 @@ -# Copyright 2024 UW-IT, University of Washington +# Copyright 2025 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.conf import settings From 157ec6d7f9071612c0709e7e8ca4510321763f4c Mon Sep 17 00:00:00 2001 From: Jim Laney Date: Wed, 15 Jan 2025 12:14:40 -0800 Subject: [PATCH 3/4] use the correct cicd file --- .github/workflows/cicd.yml | 100 +++---------------------------------- 1 file changed, 8 insertions(+), 92 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index bd1a2f6c..1bae31b9 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -1,48 +1,14 @@ -# -# Example GitHub Actions config that drives UW-IT AXD2 integration and deployment -# -# Preconditions: -# -# 1) Application docker build is based on django-container -# -# 2) Application test suite is kicked off in docker/test.sh -# -# 3) Application repo has access to the two secrets -# at https://github.com/organizations/uw-it-aca/settings/secrets: -# -# GH_AUTH_TOKEN: Grants access to private flux deployment repo -# GCP_JSON_KEY: Grants access to Google Cloud Registry -# -# To adapt this config file to a specific django project: -# -# 1) Set RELEASE_NAME suitable for deployment to k8s. RELEASE_NAME must -# match the "repo" value in docker/*-values.yml. -# -# 2) Set DJANGO_APP to the name of the django project name/directory. -# -# 3) Verify that the lists of branches for push/pull_request is appropriate, -# and add other branch names if needed. Additional branch names must -# also have steps defined in the deploy job -# -# 4) Confirm that the build steps are suitable. Likely they are, but -# some projects have an intermediate build step that could benefit -# from caching, so it may be useful to augment the build steps. -# --- name: Build, Test and Deploy env: - # Release name must match "repo" value in docker/*-values.yml RELEASE_NAME: mdot - DJANGO_APP: mdot -# Be sure that branches defined here have corresponding steps -# defined in the "deploy" job on: push: - branches: [main, master, qa, develop, feature/eval-me] + branches: [main, develop] pull_request: - branches: [main, master, qa, develop, feature/eval-me] + branches: [main, develop] types: [opened, reopened, synchronize] jobs: @@ -62,7 +28,7 @@ jobs: release_name: ${{ env.RELEASE_NAME }} build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 needs: context @@ -70,17 +36,6 @@ jobs: - name: Checkout Repo uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - - name: Run Python Linters - uses: uw-it-aca/actions/python-linters@main - with: - app_name: ${DJANGO_APP} - exclude_paths: 'migrations' - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -93,44 +48,14 @@ jobs: ${{ runner.os }}-buildx- - name: Build App Image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v3 with: - context: . - target: app-container tags: ${{ needs.context.outputs.image_tag }} push: false load: true cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache - - name: Build Test Image - uses: docker/build-push-action@v6 - with: - target: app-test-container - tags: app-test-container - push: false - load: true - - - name: Run Tests in Image - id: tests - shell: bash - run: >- - docker run -u root -t - -v ${PWD}:/coverage - -e DJANGO_APP="$DJANGO_APP" - -e "ENV=localdev" -e "AUTH=SAML_MOCK" - app-test-container - bash -c ". ./docker/test.sh" - - - name: Record Test Results - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash - run: | - python -m pip install --upgrade pip coverage coveralls==3.3.1 - coverage combine - coveralls - - name: Push Image to Repository if: github.event_name == 'push' uses: uw-it-aca/actions/gcr-push@main @@ -143,35 +68,25 @@ jobs: needs: [context, build] + runs-on: ubuntu-22.04 + outputs: context: ${{ steps.context.outputs.context }} - runs-on: ubuntu-22.04 - steps: - name: Checkout Repo uses: actions/checkout@v4 - name: Deployment Pipeline if: >- - contains(fromJSON('["main"]'), + contains(fromJSON('["main", "master"]'), needs.context.outputs.git_repo_branch) uses: uw-it-aca/actions/cicd-deploy@main with: release_name: ${{ env.RELEASE_NAME }} - commit_hash: ${{ needs.context.outputs.commit_hash }} - git_repo_branch: ${{ needs.context.outputs.git_repo_branch }} gh_auth_token: ${{ secrets.GH_AUTH_TOKEN }} - - - name: Deploy Evaluation Branch - if: needs.context.outputs.git_repo_branch == 'feature/eval-me' - uses: uw-it-aca/actions/cicd-deploy@main - with: - release_name: ${{ env.RELEASE_NAME }} commit_hash: ${{ needs.context.outputs.commit_hash }} git_repo_branch: ${{ needs.context.outputs.git_repo_branch }} - gh_auth_token: ${{ secrets.GH_AUTH_TOKEN }} - app_instance: eval - name: 'Surface context from executed build step' id: context @@ -193,3 +108,4 @@ jobs: gh_auth_token: ${{ secrets.GH_AUTH_TOKEN }} registry_password: ${{ secrets.GCP_JSON_KEY }} context: ${{ needs.deploy.outputs.context }} + prune_flux_preserve_count: 2 From 6250f4a278c2bd6b9eabc44135ede2374cae0105 Mon Sep 17 00:00:00 2001 From: Jim Laney Date: Fri, 17 Jan 2025 12:23:45 -0800 Subject: [PATCH 4/4] fix build status badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3fe2b620..42070835 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MDOT APP # -[![Build Status](https://github.com/uw-it-aca/mdot/workflows/Build%2C%20Test%20and%20Deploy/badge.svg?branch=master)](https://github.com/uw-it-aca/mdot/actions) +[![Build Status](https://github.com/uw-it-aca/mdot/workflows/Build%2C%20Test%20and%20Deploy/badge.svg)](https://github.com/uw-it-aca/mdot/actions) [![Coverage Status](https://coveralls.io/repos/github/uw-it-aca/mdot/badge.svg?branch=master)](https://coveralls.io/github/uw-it-aca/mdot?branch=master) This README documents whatever steps are necessary to get your application up and running.