Python API Development with FastAPI

CICD

Environment Variables

Environment variables play a critical role in configuring your CI/CD workflows. They can be defined either globally for all jobs or specifically for an individual job. This guide explains how to set these variables effectively within your workflow file while keeping them secure and appropriately scoped.

Job-Specific Environment Variables

Defining environment variables at the job level ensures that they are accessible only within that particular job. For instance, if you have a job named "job1", you can set a variable by adding an env section to that job. In the following example, the environment variable for the database hostname is set to "localhost":

name: Build and Deploy Code

on: [push, pull_request]

jobs:
  job1:
    runs-on: ubuntu-latest
    env:
      DATABASE_HOSTNAME: localhost
    steps:
      - name: Pull Git repository
        uses: actions/checkout@v2
      - name: Install Python 3.9
        uses: actions/setup-python@v2
        with:
          python-version: "3.9"
      - name: Update pip
        run: python -m pip install --upgrade pip
      - name: Install all dependencies
        run: pip install -r requirements.txt
      - name: Test with pytest
        run: |
          pip install pytest
          pytest

In this scenario, the variable DATABASE_HOSTNAME is limited to job1. When you need to add other environment-specific variables, follow the same approach to ensure each variable is available only where necessary.

Conditional Environment Variables

You can also set multiple environment variables dynamically based on certain conditions. Consider the example below in which a job runs only on a specific day:

jobs:
  weekday_job:
    runs-on: ubuntu-latest
    steps:
      - name: "Hello world when it's Monday"
        if: ${{ env.DAY_OF_WEEK == 'Mon' }}
        run: echo "Hello ${{ steps.firstname.outputs.middle_name }} ${{ steps.firstname.outputs.last_name }}, today is Monday!"
        env:
          FIRST_NAME: Mona
          middle_name: The
          LAST_NAME: Octocat

This snippet demonstrates how to set several environment variables that the job will use if the condition evaluates to true.

Global Environment Variables

For scenarios where an environment variable needs to be accessible by all jobs in your workflow, define it at the root level. The example below demonstrates setting several global environment variables:

name: Build and Deploy Code
on: [push, pull_request]

env:
  DATABASE_HOSTNAME: localhost
  DATABASE_PORT: 5432
  DATABASE_PASSWORD: password123
  DATABASE_NAME: fastapi
  DATABASE_USERNAME: postgres
  SECRET_KEY: 09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b888d3e7
  ALGORITHM: HS256
  ACCESS_TOKEN_EXPIRE_MINUTES: 30

jobs:
  job1:
    runs-on: ubuntu-latest
    steps:
      - name: Pull Git repository
        uses: actions/checkout@v2
      - name: Install Python 3.9
        uses: actions/setup-python@v2
        with:
          python-version: "3.9"
      - name: Update pip
        run: python -m pip install --upgrade pip
      - name: Install all dependencies
        run: pip install -r requirements.txt
      - name: Test with pytest
        run: |
          pip install pytest
          pytest

Here, all jobs within this workflow will have access to the global environment variables. However, keep in mind that any variable set at the job level will override the global setting for that job only.

Security Consideration

It is crucial to avoid hardcoding sensitive information such as database credentials or secret keys in your repository. For improved security, store such data as encrypted secrets in your CI/CD system so that they remain concealed yet accessible during runtime.

Handling Workflow Errors

When testing your workflow, you might encounter issues, especially if your runner lacks the necessary infrastructure. For example, if an environment variable points to a local database (localhost) that isn’t actually available on the runner, you may see SQLAlchemy connection failures:

Error: Process completed with exit code 4.

This behavior is expected when using a temporary environment like a fresh runner. To resolve this, consider setting up a temporary database on the runner or configure the workflow to use a remote database service (such as on Heroku or AWS).

Below is a snippet from a typical runner log, indicating that the environment variables are correctly used as it installs dependencies and executes tests:

pip install pytest
pytest
shell: /usr/bin/bash -c {0}
env:
  DATABASE_HOSTNAME: localhost
  DATABASE_PORT: 5432
  DATABASE_PASSWORD: password123
  DATABASE_NAME: fastapi
  DATABASE_USERNAME: postgres
  SECRET_KEY: 92e3456fabc256688167b956393799f60f4caafc6f80b83e7
  ALGORITHM: HS256
  ACCESS_TOKEN_EXPIRE_MINUTES: 30
  PYTHONPATH: ./python:./app
Collecting pytest
  Downloading pytest-6.2.5-py3-none-any.whl (280 kB)
Collecting attrs>=19.2.0
  Downloading attrs-21.2.0-py2.py3-none-any.whl (53 kB)
...

Summary

Environment variables in your CI/CD workflows can be defined at both the job and global levels, providing flexibility and scope isolation. Job-specific variables ensure that only relevant jobs have access, whereas global variables facilitate shared configurations across multiple jobs. Tailor your environment setup according to your testing environment and security requirements.

Happy coding!

Watch Video

Watch video content

Previous
Configuring Python in Pipeline