DEV Community

Cody Meadows
Cody Meadows

Posted on

Website... Unchained

Overview

The original version of my website was created for the Cloud Resume Challenge, but as I've progressed after achieving the AZ-104 certification I decided to break away from its mold a bit. I started to get more focused on Python and wanted to redo my site with Django. I also wanted to implement IAC via Terraform and CI/CD via GitHub Actions, to implement things properly.

A little overkill for a single-page site, but I thought it was a fun project and it allows me to better expand in the future.

I delved into Django a bit last summer, and IAC / CI/CD the last several months, but I want to thank the following sources for helping me get through this project:

    ├───app
    │   ├───cmeadows_tech           # Project folder
    │   ├───home                    # Main app folder
    └───infra
        ├───global                  # Global Terraform IAC
        └───web                     # Terraform IAC for running app
Enter fullscreen mode Exit fullscreen mode

Models and Views

Pretty straightforward for a single-page site. At the moment I have one model for my projects, so in the future I can more easily add, remove and modify them. You know, your basic CRUD operations.

class Project(models.Model):
    title = models.CharField(max_length=30, help_text='The title of the project')
    url = models.URLField(help_text='The URL for the project\'s repo or blog post', blank=True)
    tech = models.CharField(max_length=200, help_text='Comma separated list of technologies used by the project')
    synopsis = models.TextField(help_text='Summary of the project')
Enter fullscreen mode Exit fullscreen mode

In the future I would like to add another model for a more extended project description, akin to a blog post, so visitors can read more about my projects without having to follow through to another site.

settings.py

Cosmos MongoDB

To try and save on costs and because I like NoSQL databases, I wanted to put my database up on Cosmos DB. The easiest way to do this was with its MongoDB API and the Djongo backend.

This did come with some difficulties though, as the package dependencies were a mess after updates to Python3 and Django itself.

This open issue has an ongoing conversation on the problems I faced, and is what helped me correct my dependencies.

These packages work together:

asgiref==3.6.0
Django==4.1.4
djongo==1.3.6
dnspython==2.2.1
pymongo==3.12.3
pytz==2022.7           # This  must be installed manually, it is not included in djongo
sqlparse==0.2.4
tzdata==2022.7
Enter fullscreen mode Exit fullscreen mode
Note: Cosmos uses a different port than the default MongoDB port, so you have to specify it in the connection string.
Enter fullscreen mode Exit fullscreen mode

Azure Storage backend

I also wanted to use Azure Storage to serve my static files, which luckily was pretty straightforward to set up with the Azure Storage backend. In the future I might look into setting up a CDN to serve these files as well.

AZURE_ACCOUNT_NAME = "<storage account name>"
AZURE_CONTAINER = 'static'
AZURE_ACCOUNT_KEY = '<storage account key>'
AZURE_OVERWRITE_FILES = 'True'

STATIC_URL = 'static/'

STATIC_DIRS = [
BASE_DIR / "static"
]
Enter fullscreen mode Exit fullscreen mode

Security

Of course, this resulted in a bunch of vulnerabilities if I were to store the connection strings and keys as plaintext in settings.py, so I utilized environment variables, GitHub Secrets, and App Service Settings to secure the important bits.

DJANGO_SECRET_KEY - Used to update SECRET_KEY in production
- Secret = DJANGO_SECRET_KEY
DJANGO_DEBUG - Used to disable debug mode in production
- No Secret
DJONGO_HOST - Used to secure connection string to Cosmos MongoDB
- Secret = DJONGO_HOST        
ACCOUNT_KEY - Used to secure connection key to Azure Storage hosting static files
- Secret = ACCOUNT_KEY
Enter fullscreen mode Exit fullscreen mode

These can be added as Application Settings via the azure/appservice-settings GitHub Action

- uses: azure/appservice-settings@v1
    with:
     app-name: ${{ env.AZURE_WEBAPP_NAME }}
     mask-inputs: false
     app-settings-json: '[
          {
            "name": "ACCOUNT_KEY",
            "value": "${{ secrets.ACCOUNT_KEY }}",
            "slotSetting": false
          },
          {
            "name": "DJANGO_DEBUG",
            "value": "False",
            "slotSetting": false
          },
          {
            "name": "DJANGO_SECRET_KEY",
            "value": "${{ secrets.DJANGO_SECRET_KEY }}",
            "slotSetting": false
          },
          {
            "name": "DJONGO_HOST",
            "value": "${{ secrets.DJONGO_HOST }}",
            "slotSetting": false
          }
        ]'
     general-settings-json: '{"linuxFxVersion": "PYTHON|${{ env.PYTHON_VERSION }}"}'
Enter fullscreen mode Exit fullscreen mode

Terraform

I have two main Terraform configurations where I am utilizing the Azure Backend and a Service Principal for authentication, as mentioned earlier.

  1. global - This is used to define the resource group and storage account used by both the remote state and static files. Using the same storage account for both probably isn't considered a best practice, but I thought it acceptable for a small personal project where everything would likely share the same lifecycle
  2. web - This defines the infrastructure my Django app needs to run. The Cosmos DB, the static container, and the App Service

Security

For authentication, I am using a Service Principal and setting the below environment variables during the workflow run, with their values retrieved from repo secrets.

ARM_CLIENT_ID
ARM_CLIENT_SECRET
ARM_SUBSCRIPTION_ID 
ARM_TENANT_ID 
Enter fullscreen mode Exit fullscreen mode

Pipelines

Just two pipelines at the moment

  1. deploy.yml - Not well-named, but is what I used to deploy my web Terraform configuration. I want to thank Facuno Gauna for his wonderful article on deploying Terraform with GitHub Actions
  2. appcontent.yml - Used to deploy my app content to the Azure App Service. Using this sample provided by Microsoft, with very minor modifications.

Dockerfile

I started creating a Dockerfile with the intention of containerizing, which I still might, but haven't completed due to deciding on Azure App Service hosting instead of a container instance. It really only needs environment variables for settings.py at this point, then publishing somewhere.

Top comments (0)