Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore code quality tooling #240

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
673d763
Revert "Remove Hatch"
jonct Jul 17, 2024
350992b
Substitute third-party general build script plugin
jonct Jul 18, 2024
65dd6e9
Stand up some rudimentary unit tests
jonct Jul 18, 2024
48acc57
See what GitHub Actions thinks of build-and-test
jonct Jul 18, 2024
e015556
Curse at GitHub inconsistency
jonct Jul 18, 2024
be99ce2
Ask for it by name
jonct Jul 18, 2024
0cfad8a
Be less dummy
jonct Jul 18, 2024
ae1278e
Tolerate a little schlubbiness
jonct Jul 18, 2024
24011b4
Disable TrueNAS-specific test for CI environment
jonct Jul 18, 2024
3bd3327
Deconflict build artifact
jonct Jul 18, 2024
26f025e
Switch loyalties
jonct Jul 18, 2024
3cc95f3
Tidy up comments
jonct Jul 18, 2024
acea434
Take another run at edit/build/testing in place
jonct Aug 8, 2024
eae49de
Insert the dot of destiny
jonct Aug 8, 2024
721ab5d
Ensure dist directory during build
jonct Aug 8, 2024
8d2d9a0
Move project scripts into a subdirectory
jonct Aug 9, 2024
ab2d03e
Merge GitHub actions
jonct Aug 9, 2024
2f6f12e
Tune the runner’s network before code checkout
jonct Aug 9, 2024
2cde918
Resolve unit test module search path
jonct Aug 9, 2024
b53956f
Build distribution without going through Hatch
jonct Aug 9, 2024
938e76f
Ensure jlmkr can run from an alternate directory
jonct Aug 9, 2024
ec72f61
Upload artifacts before bolting them down
jonct Aug 9, 2024
1fab73a
Add testing to the developer notes
jonct Aug 9, 2024
fb8b8ff
Hunt trailing whitespace
jonct Aug 9, 2024
97d5873
Strike unused import
jonct Aug 9, 2024
0fb034f
Add integration test runner using temp ZFS pools
jonct Aug 9, 2024
79683af
Install zfsutils-linux on GitHub runner
jonct Aug 9, 2024
8441793
Probe the GitHub Runner environment
jonct Aug 9, 2024
977f9f7
Load ZFS; mark test scripts executable
jonct Aug 9, 2024
5ec22c1
Sweep abandoned jails from temporary test pools
jonct Aug 10, 2024
5c8b410
Document lifted restriction on test stragglers
jonct Aug 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 62 additions & 29 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the workflow will run
Expand All @@ -13,28 +11,61 @@ on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
test:
# The type of runner that the job will run on

build-and-test:
name: Build jlmkr tool
runs-on: ubuntu-24.04
defaults:
run:
shell: bash
# Steps represent a sequence of tasks that will be executed as part of the job
strategy:
matrix:
python-version:
- "3.11" # TrueNAS SCALE 24.04 Dragonfish
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11' # TrueNAS SCALE 24.04 Dragonfish
- name: Tune GitHub-hosted runner network
# hat tip: <https://github.com/pypa/hatch/issues/669>

- name: Tune GitHub network
uses: smorimoto/tune-github-hosted-runner-network@v1

##########################################################
## Build software for distribution; perform code-level QA

- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: 'pyproject.toml'
cache: 'pip'

- name: Install Hatch
run: pip install hatch

- name: Build distribution
run: python3 -m scripts.build

- name: Run style check
run: hatch fmt --check || echo IGNORING ADVISORIES

- name: Run unit tests
run: hatch test --cover

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
path: |
dist/jlmkr
dist/jlmkr-*.zip
if-no-files-found: error

##########################################################
## Build software for distribution; perform code-level QA

# Create a network namespace in the GitHub-hosted runner VM,
# simulating a primary bridge network on TrueNAS SCALE
- name: Install packages and setup networking
- name: Prepare for integration tests
run: |
sudo -s <<END

Expand All @@ -43,7 +74,7 @@ jobs:
echo 'nameserver 1.1.1.1' > /etc/resolv.conf

apt-get update
apt-get install -qq -y systemd-container
apt-get install -qq -y systemd-container zfsutils-linux

cat <<NETWORKCONFIG >/etc/systemd/network/10-br1.network
[Match]
Expand All @@ -67,14 +98,17 @@ jobs:

iptables -I DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I DOCKER-USER -i br1 -o eth0 -j ACCEPT

modprobe zfs
END

- name: Examine the GitHub-hosted runner environment
- name: Inspect the runtime environment
run: |
uname -r
cat /etc/os-release
python3 --version
ip addr
zfs version

# # TODO: create zpool with virtual disks, create jailmaker dataset and test jlmkr.py from there
# # https://medium.com/@abaddonsd/zfs-usage-with-virtual-disks-62898064a29b
Expand All @@ -87,19 +121,18 @@ jobs:
# zpool --version
# END

- name: Build
run: |
python3 -m zipapp src/jlmkr -p "/usr/bin/env python3" -o jlmkr

- uses: actions/upload-artifact@v4
with:
path: jlmkr

# Run multiple commands using the runners shell
- name: Run the test script
- name: Run integration tests
env:
PYTHONUNBUFFERED: 1
run: |
sudo ln dist/jlmkr .
sudo chown 0:0 jlmkr ./test/test-jlmkr
sudo bash ./test/test-jlmkr
sudo ./test/test.sh

- name: Run secondary integration tests
env:
PYTHONUNBUFFERED: 1
run: |
chmod +x scripts/*
python3 -m scripts.test
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ dist/
__pycache__/
*.py[cod]

.pytest_cache/
.ruff_cache/
/.pytest_cache/
/.ruff_cache/
/.coverage
52 changes: 52 additions & 0 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Developer notes

## Building Jailmaker

No external dependencies are needed to perform a simple build from the project directory.

python3 -m scripts.build

Anything beyond this is *completely optional…*

## Development mode

Jailmaker's user-facing design and important safety features tend to get in the way of rapid development. To run directly from the editable source code, create an external configuration file.

mkdir -p ~/.local/share
cat <<EOF >~/.local/share/jailmaker.conf
[DEFAULT]
ignore_owner = 1
jailmaker_dir = /mnt/pool/jailmaker
EOF

If present, this file will override traditional self-detection of the Jailmaker directory.

## Code quality tooling

Additional tools for testing, coverage, and code quality review are available through [Hatch][1]. Install them in a self-contained, disposable virtual environment.

python3 -m venv --without-pip .venv
curl -OL https://bootstrap.pypa.io/pip/pip.pyz
.venv/bin/python3 pip.pyz install pip hatch
rm pip.pyz

Activate a session inside the virtual environment. (For more information see the `venv` [tutorial][2].)

source .venv/bin/activate

Use `hatch` to build, test, lint, etc.

hatch build

hatch fmt --check

hatch test --cover

*Please don't forget to run tests before submitting a pull request!*

## Integration testing

See [`test/README.md`](./test/README.md).

[1]: https://hatch.pypa.io/
[2]: https://docs.python.org/3/tutorial/venv.html
96 changes: 96 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
#
# SPDX-License-Identifier: LGPL-3.0-only

[project]
name = "jlmkr"
description = "Build and manage jails on TrueNAS SCALE"
dynamic = ["version"]
readme = "README.md"
classifiers = [
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Private :: Do Not Upload",
]
authors = [
{name = "Jip-Hop"},
{name = "Lockszmith-GH"},
{name = "jonct"},
]
maintainers = [
{name = "Jip-Hop"},
]
requires-python = ">=3.11"

[project.urls]
GitHub = "https://github.com/Jip-Hop/jailmaker"

[build-system]
requires = ["hatchling", "hatch-build-scripts"]
build-backend = "hatchling.build"

[tool.hatch.version]
path = "src/jlmkr/__main__.py" # or source = "vcs"

[project.scripts]
jlmkr = "jlmkr:__main__"


#### BUILD


[[tool.hatch.build.hooks.build-scripts.scripts]]
out_dir = "dist"
commands = ['python -m scripts.build']
artifacts = [
'dist/jlmkr',
'dist/jlmkr-*.zip'
]
clean_artifacts = true
clean_out_dir = true


#### TEST


[[tool.hatch.envs.hatch-test.matrix]]
python = [
"3.11", # TrueNAS SCALE 24.04 Dragonfish
]

[tool.pytest.ini_options]
pythonpath = "src/jlmkr"
addopts = [
"--import-mode=importlib",
]


#### ANALYZE


[tool.hatch.envs.types]
extra-dependencies = [
"mypy>=1.0.0",
]

[tool.hatch.envs.types.scripts]
check = "mypy --install-types --non-interactive {args:src/jlmkr tests}"


#### COVERAGE


[tool.coverage.run]
source_pkgs = ["jlmkr", "tests"]
branch = true
parallel = true

[tool.coverage.paths]
jlmkr = ["src/jlmkr"]
tests = ["tests"]

[tool.coverage.report]
exclude_lines = [
"no cov",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
71 changes: 71 additions & 0 deletions scripts/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
#
# SPDX-License-Identifier: LGPL-3.0-only

# hat tip: <https://github.com/dairiki/hatch-zipped-directory/blob/master/hatch_zipped_directory/builder.py>

import os
import sys
from io import BytesIO
from pathlib import Path
from typing import Any, Callable, Iterable
from zipapp import create_archive
from zipfile import ZipFile, ZIP_DEFLATED

from src.jlmkr.__main__ import __version__ as VERSION

PROJECT_PATH = Path.cwd()
SRC_PATH = Path('./src/jlmkr')
DIST_PATH = Path('./dist')

# 10 lines will conveniently match the default of head(1)
PREAMBLE = f'''#!/usr/bin/env python3

jlmkr {VERSION}

Persistent Linux 'jails' on TrueNAS SCALE to install software (k3s, docker, portainer, podman, etc.) with full access to all files via bind mounts.

SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
SPDX-License-Identifier: LGPL-3.0-only

-=-=-=- this is a zip file -=-=-=- what follows is binary -=-=-=-
'''


def build_tool() -> Path:

# generate zipapp source archive
pyzbuffer = BytesIO()
create_archive(SRC_PATH, target=pyzbuffer,
interpreter='=PLACEHOLDER=',
compressed=True)
zipdata = pyzbuffer.getvalue().removeprefix(b"#!=PLACEHOLDER=\n")

# output with preamble
tool_path = DIST_PATH.joinpath('jlmkr')
with open(tool_path, 'wb') as f:
f.write(PREAMBLE.encode())
f.write(zipdata)
os.chmod(tool_path, 0o755)
return tool_path


def build_zip() -> Path:
zip_path = DIST_PATH.joinpath(f'jlmkr-{VERSION}.zip')
tool_path = DIST_PATH.joinpath('jlmkr')
attachments = ['README.md', 'LICENSE']

# include the tool as-is (with the uncompressed preamble),
# then compress and attach other hangers-on
with ZipFile(zip_path, 'w') as zip:
zip.write(tool_path)
for attachment in attachments:
path = PROJECT_PATH.joinpath(attachment)
zip.write(path, compress_type=ZIP_DEFLATED)
return zip_path


if __name__ == '__main__':
DIST_PATH.mkdir(exist_ok=True)
print(build_tool())
print(build_zip())
Loading