Skip to content

Commit

Permalink
add python 3.12 support and fix development dependencies in dockerfile (
Browse files Browse the repository at this point in the history
  • Loading branch information
gsnider2195 authored Aug 26, 2024
1 parent 3e5c2d3 commit a32890e
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on: # yamllint disable-line rule:truthy rule:comments
pull_request: ~

env:
APP_NAME: "{{ cookiecutter.project_slug }}"
APP_NAME: "{{ cookiecutter.app_slug }}"

jobs:
ruff-format:
Expand Down Expand Up @@ -103,6 +103,10 @@ jobs:
uses: "actions/checkout@v4"
- name: "Setup environment"
uses: "networktocode/gh-action-setup-poetry-environment@v6"
- name: "Constrain Nautobot version and regenerate lock file"
env:
INVOKE_NAUTOBOT_DEV_EXAMPLE_LOCAL: "true"
run: "poetry run invoke lock --constrain-nautobot-ver --constrain-python-ver"
- name: "Set up Docker Buildx"
id: "buildx"
uses: "docker/setup-buildx-action@v3"
Expand All @@ -120,6 +124,7 @@ jobs:
build-args: |
NAUTOBOT_VER={% raw %}${{ matrix.nautobot-version }}{% endraw %}
PYTHON_VER={% raw %}${{ matrix.python-version }}{% endraw %}
CI=true
- name: "Copy credentials"
run: "cp development/creds.example.env development/creds.env"
- name: "Linting: pylint"
Expand All @@ -134,14 +139,14 @@ jobs:
strategy:
fail-fast: true
matrix:
python-version: ["3.8", "3.11"]
python-version: ["3.8", "3.12"]
db-backend: ["postgresql"]
nautobot-version: ["stable"]
include:
- python-version: "3.11"
db-backend: "postgresql"
nautobot-version: "{{ cookiecutter.min_nautobot_version }}"
- python-version: "3.11"
- python-version: "3.12"
db-backend: "mysql"
nautobot-version: "stable"
runs-on: "ubuntu-22.04"
Expand Down Expand Up @@ -170,6 +175,7 @@ jobs:
build-args: |
NAUTOBOT_VER={% raw %}${{ matrix.nautobot-version }}{% endraw %}
PYTHON_VER={% raw %}${{ matrix.python-version }}{% endraw %}
CI=true
- name: "Copy credentials"
run: "cp development/creds.example.env development/creds.env"
- name: "Use Mysql invoke settings when needed"
Expand Down Expand Up @@ -208,7 +214,7 @@ jobs:
- name: "Set up Python"
uses: "actions/setup-python@v5"
with:
python-version: "3.11"
python-version: "3.12"
- name: "Install Python Packages"
run: "pip install poetry"
- name: "Set env"
Expand Down Expand Up @@ -243,7 +249,7 @@ jobs:
- name: "Set up Python"
uses: "actions/setup-python@v5"
with:
python-version: "3.11"
python-version: "3.12"
- name: "Install Python Packages"
run: "pip install poetry"
- name: "Set env"
Expand Down
31 changes: 10 additions & 21 deletions nautobot-app/{{ cookiecutter.project_slug }}/development/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,29 +53,18 @@ RUN which poetry || curl -sSL https://install.python-poetry.org | python3 - && \
WORKDIR /source
COPY . /source

# Get container's installed Nautobot version as a forced constraint
# NAUTOBOT_VER may be a branch name and not a published release therefor we need to get the installed version
# so pip can use it to recognize local constraints.
RUN pip show nautobot | grep "^Version: " | sed -e 's/Version: /nautobot==/' > constraints.txt
# Build args must be declared in each stage
ARG PYTHON_VER

# Use Poetry to grab dev dependencies from the lock file
# Can be improved in Poetry 1.2 which allows `poetry install --only dev`
#
# We can't use the entire freeze as it takes forever to resolve with rigidly fixed non-direct dependencies,
# especially those that are only direct to Nautobot but the container included versions slightly mismatch
RUN poetry export -f requirements.txt --without-hashes --extras all --output poetry_freeze_base.txt
RUN poetry export -f requirements.txt --without-hashes --extras all --with dev --output poetry_freeze_all.txt
RUN sort poetry_freeze_base.txt poetry_freeze_all.txt | uniq -u > poetry_freeze_dev.txt

# Install all local project as editable, constrained on Nautobot version, to get any additional
# direct dependencies of the app
RUN --mount=type=cache,target="/root/.cache/pip",sharing=locked \
pip install -c constraints.txt -e .[all]
# Constrain the Nautobot version to NAUTOBOT_VER
# In CI, this should be done outside of the Dockerfile to prevent cross-compile build failures
ARG CI
RUN if [ -z "${CI+x}" ]; then \
INSTALLED_NAUTOBOT_VER=$(pip show nautobot | grep "^Version" | sed "s/Version: //"); \
poetry add --lock nautobot@${INSTALLED_NAUTOBOT_VER} --python ${PYTHON_VER}; fi

# Install any dev dependencies frozen from Poetry
# Can be improved in Poetry 1.2 which allows `poetry install --only dev`
RUN --mount=type=cache,target="/root/.cache/pip",sharing=locked \
pip install -c constraints.txt -r poetry_freeze_dev.txt
# Install the app
RUN poetry install --extras all --with dev

COPY development/nautobot_config.py ${NAUTOBOT_ROOT}/nautobot_config.py
# !!! USE CAUTION WHEN MODIFYING LINES ABOVE
19 changes: 11 additions & 8 deletions nautobot-app/{{ cookiecutter.project_slug }}/invoke.example.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
---
{{ cookiecutter.app_name }}:
project_name: "{{ cookiecutter.app_slug }}"
nautobot_ver: "{{ cookiecutter.min_nautobot_version }}"
local: false
python_ver: "3.11"
compose_dir: "development"
compose_files:
- "docker-compose.base.yml"
- "docker-compose.redis.yml"
- "docker-compose.postgres.yml"
- "docker-compose.dev.yml"
# local: false
# compose_dir: "/full/path/to/{{ cookiecutter.project_slug }}/development"

# The following is an example of using MySQL as the database backend
# ---
# {{ cookiecutter.app_name }}:
# compose_files:
# - "docker-compose.base.yml"
# - "docker-compose.redis.yml"
# - "docker-compose.mysql.yml"
# - "docker-compose.dev.yml"
3 changes: 2 additions & 1 deletion nautobot-app/{{ cookiecutter.project_slug }}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ classifiers = [
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
packages = [
{ include = "{{ cookiecutter.app_name }}" },
Expand All @@ -29,7 +30,7 @@ include = [
]

[tool.poetry.dependencies]
python = ">=3.8,<3.12"
python = ">=3.8,<3.13"
# Used for local development
nautobot = "^{{ cookiecutter.min_nautobot_version }}"

Expand Down
46 changes: 41 additions & 5 deletions nautobot-app/{{ cookiecutter.project_slug }}/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
"""

import os
import re
from pathlib import Path
from time import sleep

from invoke.collection import Collection
from invoke.exceptions import Exit
from invoke.tasks import task as invoke_task


Expand Down Expand Up @@ -48,7 +50,7 @@ def is_truthy(arg):
namespace.configure(
{
"{{ cookiecutter.app_name }}": {
"nautobot_ver": "{{ cookiecutter.min_nautobot_version }}",
"nautobot_ver": "2.3.1",
"project_name": "{{ cookiecutter.app_slug }}",
"python_ver": "3.11",
"local": False,
Expand Down Expand Up @@ -205,17 +207,51 @@ def generate_packages(context):
run_command(context, command)


def _get_docker_nautobot_version(context, nautobot_ver=None, python_ver=None):
"""Extract Nautobot version from base docker image."""
if nautobot_ver is None:
nautobot_ver = context.{{ cookiecutter.app_name }}.nautobot_ver
if python_ver is None:
python_ver = context.{{ cookiecutter.app_name }}.python_ver
dockerfile_path = os.path.join(context.{{ cookiecutter.app_name }}.compose_dir, "Dockerfile")
base_image = context.run(f"grep --max-count=1 '^FROM ' {dockerfile_path}", hide=True).stdout.strip().split(" ")[1]
base_image = base_image.replace(r"${NAUTOBOT_VER}", nautobot_ver).replace(r"${PYTHON_VER}", python_ver)
pip_nautobot_ver = context.run(f"docker run --rm --entrypoint '' {base_image} pip show nautobot", hide=True)
match_version = re.search(r"^Version: (.+)$", pip_nautobot_ver.stdout.strip(), flags=re.MULTILINE)
if match_version:
return match_version.group(1)
else:
raise Exit(f"Nautobot version not found in Docker base image {base_image}.")


@task(
help={
"check": (
"If enabled, check for outdated dependencies in the poetry.lock file, "
"instead of generating a new one. (default: disabled)"
)
),
"constrain_nautobot_ver": (
"Run 'poetry add nautobot@[version] --lock' to generate the lockfile, "
"where [version] is the version installed in the Dockerfile's base image. "
"Generally intended to be used in CI and not for local development. (default: disabled)"
),
"constrain_python_ver": (
"When using `constrain_nautobot_ver`, further constrain the nautobot version "
"to python_ver so that poetry doesn't complain about python version incompatibilities. "
"Generally intended to be used in CI and not for local development. (default: disabled)"
),
}
)
def lock(context, check=False):
"""Generate poetry.lock inside the Nautobot container."""
run_command(context, f"poetry {'check' if check else 'lock --no-update'}")
def lock(context, check=False, constrain_nautobot_ver=False, constrain_python_ver=False):
"""Generate poetry.lock file."""
if constrain_nautobot_ver:
docker_nautobot_version = _get_docker_nautobot_version(context)
command = f"poetry add --lock nautobot@{docker_nautobot_version}"
if constrain_python_ver:
command += f" --python {context.{{ cookiecutter.app_name }}.python_ver}"
else:
command = f"poetry {'check' if check else 'lock --no-update'}"
run_command(context, command)


# ------------------------------------------------------------------------------
Expand Down

0 comments on commit a32890e

Please sign in to comment.