diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 59bcb2c..0000000 --- a/.coveragerc +++ /dev/null @@ -1,3 +0,0 @@ -[run] -omit = - tests/** diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 79c9a45..79ebe7e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,34 +1,19 @@ -# This workflow runs the publication to pypi name: release on: release: types: [published] workflow_dispatch: {} jobs: - build-38: + publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - name: Run release + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: astral-sh/setup-uv@v5 + - name: Build sdist and wheel + run: uv build + - name: Publish to PyPI + run: uv publish env: - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - PYTHON_VERSION: "3.8" - run: REF="${{ github.ref }}" ./scripts/release.sh - build-39: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: Run release - env: - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - PYTHON_VERSION: "3.9" - run: REF="${{ github.ref }}" ./scripts/release.sh - build-310: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: Run release - env: - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - PYTHON_VERSION: "3.10" - run: REF="${{ github.ref }}" ./scripts/release.sh + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8e19d96..cd6e355 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,4 +1,3 @@ -# This workflow runs the python unit tests name: tests on: push: @@ -7,21 +6,16 @@ on: branches: [main] workflow_dispatch: {} jobs: - build-310: + test: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: astral-sh/setup-uv@v5 - name: Run unit tests - run: docker build . --target=test --build-arg PYTHON_VERSION=3.10 - build-311: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: Run unit tests - run: docker build . --target=test --build-arg PYTHON_VERSION=3.11 - build-312: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: Run unit tests - run: docker build . --target=test --build-arg PYTHON_VERSION=3.12 + run: uv run --python ${{ matrix.python-version }} --extra test pytest diff --git a/.gitignore b/.gitignore index 0acecfd..240c36a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ reports htmlcov .coverage .bash_history +uv.lock +*_pb2.py +*_pb2_grpc.py diff --git a/.isort.cfg b/.isort.cfg deleted file mode 100644 index 8e62dc4..0000000 --- a/.isort.cfg +++ /dev/null @@ -1,9 +0,0 @@ -[settings] -profile=black -from_first=true -import_heading_future=Future -import_heading_stdlib=Standard -import_heading_thirdparty=Third Party -import_heading_firstparty=First Party -import_heading_localfolder=Local -known_localfolder=tls_test_tools,tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2cd5719..c97d36a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,15 @@ repos: - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.1.2 + rev: v3.0.0 hooks: - id: prettier - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 24.3.0 hooks: - id: black exclude: imports - additional_dependencies: ["click==8.0.4"] - repo: https://github.com/PyCQA/isort - rev: 5.11.5 + rev: 5.13.2 hooks: - id: isort exclude: imports diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40d17f5..e7d389d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,22 +48,27 @@ Improvements to existing functionality are tracked as [GitHub issues using the U ### Set up your dev environment -#### Using Docker +#### Using uv -The easiest way to get up and running is to use the dockerized development environment which you can launch using: +The recommended way to get up and running is to use [`uv`](https://github.com/astral-sh/uv) for managing the Python environment: ```sh -make develop -``` +# Install dependencies including test tools +uv sync --extra test + +# Run tests +uv run pytest -Within the `develop` shell, any of the `make` targets that do not require `docker` can be run directly. The shell has the local files mounted, so changes to the files on your host machine will be reflected when commands are run in the `develop` shell. +# Run formatting +uv run pre-commit run --all-files +``` #### Locally You can also develop locally using standard python development practices. You'll need to install the dependencies for the unit tests. It is recommended that you do this in a virtual environment such as [`conda`](https://docs.conda.io/en/latest/miniconda.html) or [`pyenv`](https://github.com/pyenv/pyenv) so that you avoid version conflicts in a shared global dependency set. ```sh -pip install -r requirements_test.txt +pip install -e ".[test]" ``` ### Run unit tests @@ -74,14 +79,19 @@ Running the tests is as simple as: make test ``` -If you want to use the full set of [`pytest` CLI arguments](https://docs.pytest.org/en/6.2.x/usage.html), you can run the `scripts/run_tests.sh` script directly with any arguments added to the command. +If you want to use the full set of [`pytest` CLI arguments](https://docs.pytest.org/en/6.2.x/usage.html), you can run the `scripts/run_tests.sh` script directly with any arguments added to the command. For example, to run only a single test without capturing output, you can do: + +```sh +./scripts/run_tests.sh tests/test_tls_test_tools.py +``` ### Code formatting -This project uses [pre-commit](https://pre-commit.com/) to enforce coding style using [black](https://github.com/psf/black). To set up `pre-commit` locally, you can: +This project uses [pre-commit](https://pre-commit.com/) to enforce coding style using [black](https://github.com/psf/black) and [isort](https://pycqa.github.io/isort/). To set up `pre-commit` locally, you can: ```sh pip install pre-commit +pre-commit install ``` Coding style is enforced by the CI tests, so if not installed locally, your PR will fail until formatting has been applied. @@ -97,7 +107,7 @@ Unsure where to begin contributing? You can start by looking through these issue To contribute to this repo, you'll use the Fork and Pull model common in many open source repositories. For details on this process, watch [how to contribute](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). -When ready, you can create a pull request. Pull requests are often referred to as "PR". In general, we follow the standard [github pull request](https://help.github.com/en/articles/about-pull-requests) process. Follow the template to provide details about your pull request to the maintainers. +When ready, you can create a pull request. Pull requests are often referred to as "PR". In general, we follow the standard [github pull request](https://help.github.com/en/articles/about-pull-request) process. Follow the template to provide details about your pull request to the maintainers. Before sending pull requests, make sure your changes pass tests. @@ -110,6 +120,13 @@ Once you've [created a pull request](#how-to-contribute), maintainers will revie - Write detailed commit messages - Break large changes into a logical series of smaller patches, which are easy to understand individually and combine to solve a broader issue - +1. Tag the release: `git tag -a vX.Y.Z -m "Release vX.Y.Z"` +2. Push the tag: `git push origin vX.Y.Z` +3. Create a GitHub release for the tag +4. The CI will automatically build and publish to PyPI diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 39a1117..0000000 --- a/Dockerfile +++ /dev/null @@ -1,65 +0,0 @@ -## Base ######################################################################## -# -# This phase sets up dependencies for the other phases -## -ARG PYTHON_VERSION=3.10 -ARG BASE_IMAGE=python:${PYTHON_VERSION}-slim -FROM ${BASE_IMAGE} as base - -# This image is only for building, so we run as root -WORKDIR /src - -# Install build, test, and publish dependencies -COPY requirements.txt requirements_test.txt /src/ -RUN true && \ - apt-get update -y && \ - apt-get install make git libatomic1 gcc -y && \ - apt-get clean autoclean && \ - apt-get autoremove --yes && \ - pip install pip --upgrade && \ - pip install twine pre-commit && \ - pip install -r /src/requirements.txt && \ - pip install -r /src/requirements_test.txt && \ - true - -## Test ######################################################################## -# -# This phase runs the unit tests for the library -## -FROM base as test -COPY . /src -ARG RUN_FMT="true" -RUN true && \ - ./scripts/run_tests.sh && \ - RELEASE_DRY_RUN=true RELEASE_VERSION=0.0.0 \ - ./scripts/publish.sh && \ - ./scripts/fmt.sh && \ - true - -## Releasing disabled until OSS set up! - -## Release ##################################################################### -# -# This phase builds the release and publishes it to pypi -## -FROM test as release -ARG PYPI_TOKEN -ARG RELEASE_VERSION -ARG RELEASE_DRY_RUN -RUN ./scripts/publish.sh - -## Release Test ################################################################ -# -# This phase installs the indicated version from PyPi and runs the unit tests -# against the installed version. -## -FROM base as release_test -ARG RELEASE_VERSION -ARG RELEASE_DRY_RUN -COPY ./tests /src/tests -COPY ./scripts/run_tests.sh /src/scripts/run_tests.sh -COPY ./scripts/install_release.sh /src/scripts/install_release.sh -RUN true && \ - ./scripts/install_release.sh && \ - ./scripts/run_tests.sh && \ - true diff --git a/Makefile b/Makefile index d993579..c85be12 100644 --- a/Makefile +++ b/Makefile @@ -9,28 +9,12 @@ help: ## Display this help. .PHONY: test test: ## Run the unit tests - ./scripts/run_tests.sh - -.PHONY: test.develop -test.develop: develop.build ## Run the unit tests in the develop shell - ./scripts/develop.sh ./scripts/run_tests.sh + uv run --extra test pytest .PHONY: fmt fmt: ## Run code formatting ./scripts/fmt.sh -.PHONY: wheel -wheel: ## Build release wheels - ./scripts/build_wheel.sh - -##@ Develop - -PYTHON_VERSION ?= 3.8 - -.PHONY: develop.build -develop.build: ## Build the development environment container - docker build . --target=base -t tls-test-tools-develop --build-arg PYTHON_VERSION=${PYTHON_VERSION} - -.PHONY: develop -develop: develop.build ## Run the develop shell with the local codebase mounted - ./scripts/develop.sh +.PHONY: build +build: ## Build sdist and wheel + uv build diff --git a/README.md b/README.md index 22b0c14..23764d2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TLS Test Tools -This project is a collection of tools for managing ephemeral TLS secrets in unit tests. +This project is a library of tools for managing ephemeral TLS secrets in unit tests. When writing code that needs to either host a server with TLS enabled or make connections to a TLS enabled server, it's often difficult to write succinct unit tests that exercise these connections. This package aims to fix that! It provides @@ -9,8 +9,18 @@ ports to host temporary servers on. ## Installation -To install, simply use `pip` +To install, simply use `pip`: ```sh pip install tls-test-tools ``` + +Or use `uv` for faster installation: + +```sh +uv add tls-test-tools +``` + +## Development + +See [CONTRIBUTING.md](CONTRIBUTING.md) for information on setting up a development environment and contributing to this project. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..891f954 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,86 @@ +# Copyright 2023 IBM All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[build-system] +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" + +[project] +name = "tls_test_tools" +dynamic = ["version"] +description = "A set of tools to quickly write unit tests for (m)TLS communication" +readme = "README.md" +license = "Apache-2.0" +requires-python = ">=3.10" +authors = [ + { name = "Gabe Goodhart", email = "gabe.l.hart@gmail.com" }, +] +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", +] +dependencies = [ + "alchemy-logging>=1.0.3", + "cryptography>=36.0.2", +] + +[project.optional-dependencies] +test = [ + "Flask>=2.2.3,<4", + "grpcio>=1.80.0,<2", + "protobuf>=7,<8", + "pytest>=6.2.5", + "pytest-cov>=3.0.0", + "requests>=2.28.2,<3", +] + +[project.urls] +Homepage = "https://github.com/IBM/tls-test-tools" + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.build.targets.sdist] +include = ["tls_test_tools/", "LICENSE", "README.md"] + +[tool.hatch.build.targets.wheel] +packages = ["tls_test_tools"] + +[tool.pytest.ini_options] +addopts = [ + "--cov-config=.coveragerc", + "--cov=tls_test_tools", + "--cov-report=term", + "--cov-report=html", + "--cov-fail-under=100.0", + "-W", "error", +] + +[tool.coverage.run] +omit = ["tests/**"] + +[tool.isort] +profile = "black" +from_first = true +import_heading_future = "Future" +import_heading_stdlib = "Standard" +import_heading_thirdparty = "Third Party" +import_heading_firstparty = "First Party" +import_heading_localfolder = "Local" +known_localfolder = ["tls_test_tools", "tests"] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 44c5b06..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -alchemy-logging>=1.0.3 -cryptography>=36.0.2 diff --git a/requirements_test.txt b/requirements_test.txt deleted file mode 100644 index 462c38e..0000000 --- a/requirements_test.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Testing -pytest>=6.2.5 -pytest-cov>=3.0.0 - -# Sample servers -Flask>=2.2.3,<4 -grpcio>=1.80.0,<2 -requests>=2.28.2,<3 -protobuf>=7,<8 diff --git a/scripts/build_wheel.sh b/scripts/build_wheel.sh deleted file mode 100755 index be64451..0000000 --- a/scripts/build_wheel.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash - -# Version of the library that we want to tag our wheel as -release_version=${RELEASE_VERSION:-""} -# Python tags we want to support -python_versions="py37 py38 py39 py310" -GREEN='\033[0;32m' -NC='\033[0m' - -function show_help -{ -cat <<- EOM -Usage: scripts/build_wheels.sh -v [Library Version] -p [python versions] -EOM -} - -while (($# > 0)); do - case "$1" in - -h | --h | --he | --hel | --help) - show_help - exit 2 - ;; - -p | --python_versions) - shift - python_versions="" - while [ "$#" -gt "0" ] - do - if [ "$python_versions" != "" ] - then - python_versions="$python_versions " - fi - python_versions="$python_versions$1" - if [ "$#" -gt "1" ] && [[ "$2" == "-"* ]] - then - break - fi - shift - done - ;; - -v | --release_version) - shift; release_version="$1";; - *) - echo "Unkown argument: $1" - show_help - exit 2 - ;; - esac - shift -done - -if [ "$release_version" == "" ]; then - echo "ERROR: a release version for the library must be specified." - show_help - exit 1 -else - echo -e "Building wheels for version: ${GREEN}${release_version}${NC}" - sleep 2 -fi -for python_version in $python_versions; do - echo -e "${GREEN}Building wheel for Python version [${python_version}]${NC}" - RELEASE_VERSION=$release_version python3 setup.py bdist_wheel --python-tag ${python_version} clean --all - echo -e "${GREEN}Done building wheel for Python version [${python_version}]${NC}" - sleep 1 -done diff --git a/scripts/develop.sh b/scripts/develop.sh deleted file mode 100755 index 73c661a..0000000 --- a/scripts/develop.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -touch .bash_history -docker run --rm -it \ - --entrypoint bash \ - -w /src \ - -v ${PWD}:/src \ - -v ${PWD}/.bash_history:/root/.bash_history \ - tls-test-tools-develop $@ diff --git a/scripts/install_release.sh b/scripts/install_release.sh deleted file mode 100755 index 7194c22..0000000 --- a/scripts/install_release.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash - -################################################################################ -# This script is used to execute unit tests against a recent release without the -# code held locally. It's intended to be used inside of the `release_test` -# phase of the central Dockerfile. -################################################################################ - -# Make sure RELEASE_VERSION is defined -if [ -z ${RELEASE_VERSION+x} ] -then - echo "RELEASE_VERSION must be set" - exit 1 -fi - -# The name of the library we're testing -LIBRARY_NAME="tls_test_tools" - -# 10 minutes max for trying to install the new version -MAX_DURATION="${MAX_DURATION:-600}" - -# Time to wait between attempts to install the version -RETRY_SLEEP=5 - -# Retry the install until it succeeds -start_time=$(date +%s) -success="0" -while [ "$(expr "$(date +%s)" "-" "${start_time}" )" -lt "${MAX_DURATION}" ] -do - pip cache purge - pip install ${LIBRARY_NAME}==${RELEASE_VERSION} - exit_code=$? - if [ "$exit_code" != "0" ] - then - echo "Trying again in [${RETRY_SLEEP}s]" - sleep ${RETRY_SLEEP} - else - success="1" - break - fi -done - -# If the install didn't succeed, exit with failure -if [ "$success" == "0" ] -then - echo "Unable to install [${LIBRARY_NAME}==${RELEASE_VERSION}]!" - exit 1 -fi diff --git a/scripts/publish.sh b/scripts/publish.sh deleted file mode 100755 index b80885e..0000000 --- a/scripts/publish.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -# Run from the base of the python directory -cd $(dirname ${BASH_SOURCE[0]})/.. - -# Clear out old publication files in case they're still around -rm -rf build dist *.egg-info/ - -# Build -py_tag="py$(echo $PYTHON_VERSION | cut -d'.' -f 1,2 | sed 's,\.,,g')" -./scripts/build_wheel.sh -v $RELEASE_VERSION -p $py_tag - -# Publish to PyPi -if [ "${RELEASE_DRY_RUN}" != "true" ] -then - un_arg="" - pw_arg="" - if [ "$PYPI_TOKEN" != "" ] - then - un_arg="--username __token__" - pw_arg="--password $PYPI_TOKEN" - fi - twine upload $un_arg $pw_arg dist/* -else - echo "Release DRY RUN" -fi - -# Clean up -rm -rf build dist *.egg-info/ diff --git a/scripts/release.sh b/scripts/release.sh deleted file mode 100755 index 0fffd33..0000000 --- a/scripts/release.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -# Run from the project root -cd $(dirname ${BASH_SOURCE[0]})/.. - -# Get the tag for this release -tag=$(echo $REF | cut -d'/' -f3-) - -# Build the docker phase that will release and then test it -docker build . \ - --target=release_test \ - --build-arg RELEASE_VERSION=$tag \ - --build-arg PYPI_TOKEN=${PYPI_TOKEN:-""} \ - --build-arg RELEASE_DRY_RUN=${RELEASE_DRY_RUN:-"false"} \ - --build-arg PYTHON_VERSION=${PYTHON_VERSION:-"3.7"} diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index 496b302..601866f 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -5,8 +5,7 @@ BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$BASE_DIR" FAIL_THRESH=100.0 -python3 -m pytest \ - --cov-config=.coveragerc \ +uv run pytest \ --cov=tls_test_tools \ --cov-report=term \ --cov-report=html \ diff --git a/setup.py b/setup.py deleted file mode 100644 index 16c5ce4..0000000 --- a/setup.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2023 IBM All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""A setuptools setup module for tls_test_tools""" - -# Standard -import os - -# Third Party -from setuptools import setup - -# Read the README to provide the long description -python_base = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(python_base, "README.md"), "r") as handle: - long_description = handle.read() - -# Read version from the env -version = os.environ.get("RELEASE_VERSION") -assert version is not None, "Must set RELEASE_VERSION" - -# Read in the requirements -with open(os.path.join(python_base, "requirements.txt"), "r") as handle: - requirements = handle.read() - -setup( - name="tls_test_tools", - version=version, - description="A set of tools to quickly write unit tests for (m)TLS communication", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/IBM/tls-test-tools", - author="Gabe Goodhart", - author_email="gabe.l.hart@gmail.com", - license="APACHE", - classifiers=[ - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - ], - packages=["tls_test_tools"], - install_requires=requirements, -)