From beb92e6eeb9d0fb63c0f82d4298143d16c50cc72 Mon Sep 17 00:00:00 2001 From: Dietrich Wall Date: Sun, 13 Sep 2026 13:40:27 +0200 Subject: [PATCH 1/6] cleanup and restructuring, part 1: - implemented proper python package setup - removed all functionality for now - added python package and built tests - added build pipeline --- .github/workflows/ci-build.yml | 47 +++++++++++ README.md | 34 ++++---- docker-operations.py | 104 ------------------------- pyproject.toml | 34 ++++++-- repo-ops.py | 65 ---------------- requirements.txt | 2 + ssh/ssh-keys.py => scripts/ssh_keys.py | 67 ++++++++-------- tests/__init__.py | 0 tests/conftest.py | 104 ------------------------- tests/test_package.py | 38 +++++++++ tests/test_ssh_keys.py | 68 ---------------- 11 files changed, 166 insertions(+), 397 deletions(-) create mode 100644 .github/workflows/ci-build.yml delete mode 100755 docker-operations.py delete mode 100755 repo-ops.py create mode 100644 requirements.txt rename ssh/ssh-keys.py => scripts/ssh_keys.py (67%) create mode 100644 tests/__init__.py delete mode 100644 tests/conftest.py create mode 100644 tests/test_package.py delete mode 100644 tests/test_ssh_keys.py diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml new file mode 100644 index 0000000..5ef18fa --- /dev/null +++ b/.github/workflows/ci-build.yml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12'] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: 'pyproject.toml' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ".[dev]" + + - name: Run tests + run: | + pytest tests/test_package.py + + - name: Build package + run: | + pip install build + python -m build + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: python-build-${{ matrix.python-version }} + path: dist/ + retention-days: 7 + if-no-files-found: ignore + # overwrite-mode: allow-existing diff --git a/README.md b/README.md index 18e7a86..6090f6b 100644 --- a/README.md +++ b/README.md @@ -19,31 +19,29 @@ This file requires just one line containing PASSWORD=yourpassword. ssh-keygen is used for generation. This executable is available on all my Linux and Windows environments. For deployment, paramiko is used. # Testing -Tests are implemented in tests/test-ssh-keys.py -There is a test for each KEY_TYPE generation and KEY_TYPE deployment between from one to another container. -Before running the tests you have to build the docker images. Best approach is to run -``` -./docker-operations.py --build test_image -``` -This creates a docker image locally from scratch. - -This script has also some requirements on the environment. You probably want to create a virtual environment and install requirements.txt first. +Tests are implemented in tests/ directory. -If you want to test ssh-keys.py script manually, you can run -``` -./docker-operations.py --run test_image #starts only one container -./docker-operations.py --run ssh-network #starts 2 interconnected containers with docker compose +To install development dependencies (`pytest`, `python-dotenv` etc.): +```bash +pip install -e ".[dev]" ``` -This will start a container with the script inside, so you can play around without affecting your client PC. - +To run tests: +```bash +pytest ``` -./docker-operations.py --stop test_image + +Or with coverage reporting: +```bash +pytest --cov=scripts --cov-report=term-missing ``` -stops and removes all containers, defined from the provided Dockerfile. -Since the purpose of the scripts is to modify client/server PCs, the images and containers are only used for testing. +To build the package: +```bash +pip install build +python -m build +``` # Improvements / Nice to have diff --git a/docker-operations.py b/docker-operations.py deleted file mode 100755 index 3319b01..0000000 --- a/docker-operations.py +++ /dev/null @@ -1,104 +0,0 @@ -#! /usr/bin/env python3 - -import subprocess -import argparse -import docker -from docker.models.containers import Container as Container - -test_image_tag = "ssh-image" - -def get_repo_root(): - repo_root = subprocess.run(["git", "rev-parse", "--show-toplevel"],capture_output=True) - return repo_root.stdout.strip().decode('utf-8') - -def load_env() -> dict: - ''' - loads .env file if it exists in repo root - :return: dictionary of key-value pairs or empty dict if no .env file found. - :rtype: dict[Any, Any] - ''' - import os - if os.path.exists('.env'): - from dotenv import dotenv_values - config = dotenv_values(".env") - return config - return {} - -def build_image() -> bool: - print(f"building {test_image_tag}") - build_args = load_env() - client = docker.from_env() - client.images.build(path=".", tag=test_image_tag, buildargs=build_args) - print(f"Successfully built image: {test_image_tag}") - return True - -def run_container(image_tag: str) -> Container: - print(f"Running container from image: {image_tag}") - import docker.types - mounts=[docker.types.Mount(target="/home/appuser/code/", source=f"{get_repo_root()}", type="bind", read_only=False)] - client = docker.from_env() - - container = client.containers.run(image_tag, detach=True, tty=True, mounts=mounts, environment=load_env()) - print(f"Container {container.id} is running.") - print(f"enter with: ") - print(f"docker exec -it {container.name} bash") - return container - -def run_network() -> bool: - print(f"Running ssh-network using docker-compose") - repo_root = get_repo_root() - - import tests.conftest - tests.conftest.run_network(repo_root=repo_root) - containers = tests.conftest.network() - client_container = tests.conftest.client(containers) - server_container = tests.conftest.server(containers) - - print(f"enter client with:") - print(f"docker exec -it {client_container.name} bash") - print(f"enter server with:") - print(f"docker exec -it {server_container.name} bash") - return True - -def stop_container(test_image_tag): - print(f"Stopping and removing containers from image: {test_image_tag}") - client = docker.from_env() - containers = client.containers.list(all=True, filters={"ancestor": test_image_tag}) - for container in containers: - print(f"Stopping container {container.id}...") - container.stop() - print(f"Removing container {container.id}...") - container.remove() - print("All containers stopped and removed.") - return True - -def main(): - """Main function to build Docker images and run containers""" - available_images = ['test_image', 'ssh-network'] - parser = argparse.ArgumentParser(description="Builds and run different images and containers in this repo") - parser.add_argument("--build",'-b', help="Builds the Docker image", choices=available_images, default=None, nargs='?') - parser.add_argument("--run", '-r', help="Runs the Docker container", choices=available_images, default=None, nargs='?') - parser.add_argument("--stop", '-s', help="stops all containers with given tag", choices=available_images, default=None, nargs='?') - args = parser.parse_args() - - # Build the image - if args.build is not None: - if args.build == 'test_image' or args.build == 'ssh-network': - build_image() - else: - print(f"Unknown image to build: {args.build}") - - if args.run is not None: - if args.run == 'test_image': - run_container(test_image_tag) - elif args.run == 'ssh-network': - run_network() - else: - print(f"Unknown image to run: {args.run}") - - if args.stop is not None: - if args.stop == 'test_image': - stop_container(test_image_tag) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index cbeecfc..132d5a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,35 @@ +[build-system] +requires = ["setuptools >= 80.0.0", "wheel"] +build-backend = "setuptools.build_meta" + [project] name = "setup_scripts" version = "0.1.0" description = "several python packages for automation" +requires-python = ">=3.10" +dependencies = [ + "paramiko >= 3.4.0", +] -[build-system] -requires = [ - "setuptools>=60" +[project.scripts] +ssh-keys = "scripts.ssh_keys:main" + +[project.optional-dependencies] +dev = [ + "pytest >= 8.0.0", + "pytest-cov >= 4.0.0", + "pytest-mock >= 3.12.0", + "python-dotenv >= 1.0.0", ] -build-backend = "setuptools.build_meta" -[tool.setuptools] -packages = ['repo_helpers'] \ No newline at end of file +[tool.setuptools.packages.find] +include = ["scripts*"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-v" + + + + + diff --git a/repo-ops.py b/repo-ops.py deleted file mode 100755 index bbf36a4..0000000 --- a/repo-ops.py +++ /dev/null @@ -1,65 +0,0 @@ -#! /usr/bin/env python3 - -import argparse -from repo_helpers.Repo_Helper import Repo_Helper as docktools -import os.path - -helper = docktools() - -packages = \ -{ - 'repo_helper' :{ - "dir" : os.path.join(helper.repo_root(), "repo_helpers"), - "relative_dir" : "repo_helpers", - "testcommand" : "pytest -s -v tests/test_helper.py", - "mount_dir" : "/workspace/" - }, - 'ssh-keys' :{ - "dir" : os.path.join(helper.repo_root(), "ssh"), - "relative_dir" : "ssh", - "testcommand" : "pytest -s -v tests/test-ssh-keys.py", - "mount_dir" : "/workspace/" - } -} - -def main(): - """Main function to build Docker images and run containers""" - - parser = argparse.ArgumentParser(description="Builds and run different images and containers in this repo") - ops = ["build", "run", "test"] - - parser.add_argument("--package", "-p", help="selects a package", choices=packages, default=None, required=True) - parser.add_argument("--operations", "-o", help="selects the operations", choices=ops, nargs="+") - - parser.add_argument("--keep_running", - help="keeps the container running for debugging, if any started", - action="store_true", - default=False) - - args = parser.parse_args() - helper = docktools(None) - #todo: operations must be: build package, test package, release package, - # not build container, run container - for op in args.operations: - if op == "build": - helper.build_docker_image(dockerfile=packages[args.package]["dir"], image_name=args.package) - elif op == "run": - helper.start_container(image_name=args.package, container_name=args.package, mount_dir=packages[args.package]["mount_dir"]) - elif op == "test": - requirements_file = os.path.join(packages[args.package]["mount_dir"], packages[args.package]["relative_dir"], "requirements.txt") - print(requirements_file) - venv_dir=f".venv_{args.package}" - helper.create_venv_in_container(container_name=args.package, venv_dir=venv_dir) - helper.exec_in_container_venv(command=f"pip install -r {requirements_file}", container_name=args.package, venv_path=venv_dir) - helper.exec_in_container_venv(command=packages[args.package]["testcommand"], container_name=args.package, venv_path=venv_dir) - - if args.keep_running == False: - if helper.get_containers(args.package) != []: - helper.stop_container(args.package) - helper.remove_container(args.package) - else: - print(f"keeping the container {args.package} running, you can enter it with:") - print(f"docker exec -it {args.package} bash") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..be71acd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# requirements for global installation +paramiko>=3.4.0 diff --git a/ssh/ssh-keys.py b/scripts/ssh_keys.py similarity index 67% rename from ssh/ssh-keys.py rename to scripts/ssh_keys.py index 7fe1f3e..cf65113 100755 --- a/ssh/ssh-keys.py +++ b/scripts/ssh_keys.py @@ -63,7 +63,7 @@ def password_prompt() -> str: password = getpass.getpass(prompt="Enter password for deployment: ") return password -if __name__ == "__main__": +def main(): import argparse parser = argparse.ArgumentParser(description="Manage and deploy SSH keys.") @@ -81,37 +81,40 @@ def password_prompt() -> str: args = parser.parse_args() print(f"args: {args}") - if args.generate != None: - print(f"Generating {args.generate} SSH key at {args.key_path}...") + # if args.generate != None: + # print(f"Generating {args.generate} SSH key at {args.key_path}...") - if generate_ssh_key(args.key_path, args.generate) != 0: - print("Failed to generate SSH key.") - else: - print(f"SSH key with {args.generate} generated at {args.key_path}") + # if generate_ssh_key(args.key_path, args.generate) != 0: + # print("Failed to generate SSH key.") + # else: + # print(f"SSH key with {args.generate} generated at {args.key_path}") - if args.deploy_to_host != None: - print(f"Deploying SSH key to {args.deploy_to_host}...") - public_key_path = os.path.join(args.key_path, f'id_{args.generate}.pub') + # if args.deploy_to_host != None: + # print(f"Deploying SSH key to {args.deploy_to_host}...") + # public_key_path = os.path.join(args.key_path, f'id_{args.generate}.pub') - if not os.path.exists(public_key_path): - print(f"Public key not found at {public_key_path}. Cannot deploy.") - else: - password = "" - if args.password_type == "prompt": - password = password_prompt() - else: - from dotenv import dotenv_values - config = dotenv_values(args.dotenv_file) - password = config.get("PASSWORD", "") - - if password is not None and password != "": - result = deploy(public_key_path, args.deploy_to_host, password) - - if result != 0: - print("Failed to deploy SSH key.") - else: - print(f"SSH key {public_key_path} deployed to {args.deploy_to_host} successfully.") - else: - print("No password provided. Cannot deploy.") - exit(1) - exit(0) \ No newline at end of file + # if not os.path.exists(public_key_path): + # print(f"Public key not found at {public_key_path}. Cannot deploy.") + # else: + # password = "" + # if args.password_type == "prompt": + # password = password_prompt() + # else: + # from dotenv import dotenv_values + # config = dotenv_values(args.dotenv_file) + # password = config.get("PASSWORD", "") + + # if password is not None and password != "": + # result = deploy(public_key_path, args.deploy_to_host, password) + + # if result != 0: + # print("Failed to deploy SSH key.") + # else: + # print(f"SSH key {public_key_path} deployed to {args.deploy_to_host} successfully.") + # else: + # print("No password provided. Cannot deploy.") + # exit(1) + exit(0) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 7ff943d..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,104 +0,0 @@ -import pytest -import subprocess - -def create_empty_env_file(repo_root): - env_path = f"{repo_root}/.env" - import os - if os.path.exists(f"{repo_root}/.env") == False: - with open(env_path, "w") as f: - f.write("\n") - return env_path - -def run_network(repo_root): - create_empty_env_file(repo_root) - - import subprocess - compose_result = subprocess.run( - ["docker", "compose", "-f", f"{repo_root}/tests/compose.yml", "up", "-d"], - capture_output=True - ) - if compose_result.returncode != 0: - for line in compose_result.stdout.decode('utf-8').splitlines(): - print(f"{line}") - for line in compose_result.stderr.decode('utf-8').splitlines(): - print(f"{line}") - raise RuntimeError(f"docker-compose returned: {compose_result.returncode}") - -def client(containers): - for con in containers: - if con.name == 'ssh-client': - return con - raise RuntimeError("ssh-client container not found in test setup.") - -def server(containers): - for con in containers: - if con.name == 'ssh-server': - return con - raise RuntimeError("ssh-server container not found in test setup.") - -def network(): - import docker - client = docker.from_env() - containers = client.containers.list(filters={"ancestor": "ssh-image"}) - return containers - -@pytest.fixture(scope="session", autouse=True) -def repo_root() -> str: - import subprocess - git_result = subprocess.run(["git", "rev-parse", "--show-toplevel"],capture_output=True) - repo_root = git_result.stdout.strip().decode("utf-8") - return repo_root - -@pytest.fixture(scope="session") -def containers(repo_root): - run_network(repo_root) - containers = network() - yield containers - - for con in containers: - con.stop() - con.remove() - -@pytest.fixture(scope="session") -def client_container(containers): - return client(containers) - -@pytest.fixture(scope="session") -def server_container(containers): - return server(containers) - - -@pytest.fixture(scope="session", autouse=True) -def log_dir(): - dir = "test_log" - command = f"mkdir -p {dir}" - result = subprocess.run(command, shell=True) - return dir - -@pytest.fixture(scope="session", autouse=True) -def build_repo_helper(repo_root): - cmd = "python3 -m build ." - subprocess.run(args=cmd, shell=True, cwd=repo_root) - -@pytest.fixture(scope="session", autouse=True) -def repo_helper(repo_root, build_repo_helper): - cmd = "pip install ." - subprocess.run(args=cmd, shell=True, cwd=repo_root) - -containers_to_stop = [] - -@pytest.fixture(scope="function") -def cleanup(container_name: str): - #this takes all container names, that has been started during a test - # and stops it afterwards, otherwise the test will fail in the next run - print(f"started container: {container_name}") - containers_to_stop.append(container_name) - yield - for c in containers_to_stop: - subprocess.run(f"docker stop {c}", shell=True) - subprocess.run(f"docker rm {c}", shell=True) - - - - - \ No newline at end of file diff --git a/tests/test_package.py b/tests/test_package.py new file mode 100644 index 0000000..2777bc7 --- /dev/null +++ b/tests/test_package.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: © 2025 +# SPDX-License-Identifier: MIT + +import subprocess + + +def test_build_succeeds(): + """Test that building the package succeeds.""" + result = subprocess.run(['python', '-m', 'build'], capture_output=True, text=True) + + # Check exit code + assert result.returncode == 0, \ + f"Build failed with code {result.returncode}" + + # Check if dist directory exists and has wheel files + import os + assert os.path.exists('dist'), "dist directory does not exist after build" + assert any(f.endswith('.whl') for f in os.listdir('dist')), "No wheel files found" + assert any(f.endswith('.tar.gz') for f in os.listdir('dist')), "No source tarballs found" + + +def test_ssh_keys_command_available(): + """Test that ssh-keys command is available and provides help.""" + # Force reinstall in development mode + subprocess.run(['pip', 'uninstall', 'setup_scripts', '-y'], capture_output=True) + subprocess.run(['pip', 'install', '-e', '.'], capture_output=True) + + # Test that ssh-keys command is available and works + result = subprocess.run(['ssh-keys', '--help'], capture_output=True, text=True) + + # Check command exists (exit code 0) + assert result.returncode == 0, \ + f"ssh-keys command failed with code {result.returncode}, stderr: {result.stderr}" + + # Check help output contains expected content + help_output = result.stdout.lower() + assert 'ssh-keygen' in help_output or 'usage' in help_output or 'deploy' in help_output, \ + f"Help output seems incomplete, output: {help_output}" diff --git a/tests/test_ssh_keys.py b/tests/test_ssh_keys.py deleted file mode 100644 index 1de8466..0000000 --- a/tests/test_ssh_keys.py +++ /dev/null @@ -1,68 +0,0 @@ - -#generate each key once -#deploy each key to a second container - -import pytest - -@pytest.mark.skip(reason="just for debugging") -def test_show_code_directory(client_container): - print("") #newline - - cmd = "ls -la /home/appuser" - exec_result = client_container.exec_run(cmd=cmd, demux=True) - print(f"exit code: {exec_result.exit_code}") - for line in exec_result.output[0].decode('utf-8').splitlines(): - print(f"{line}") - - cmd = "ls -la /home/appuser/code/ssh/" - exec_result = client_container.exec_run(cmd=cmd, demux=True) - print(f"exit code: {exec_result.exit_code}") - for line in exec_result.output[0].decode('utf-8').splitlines(): - print(f"{line}") - -@pytest.mark.parametrize("key_type", ["rsa", "dsa", "ecdsa", "ed25519"]) -def test_ssh_keys_generation(client_container, key_type): - print("") #newline - - key_path = f"/home/appuser/keys/" - cmd=f"/home/appuser/code/ssh/ssh-keys.py --generate {key_type} --key-path={key_path}" - - print(f"running:") - print(f"{cmd}") - - exec_result = client_container.exec_run(cmd=cmd, demux=True) - print(f"exit code: {exec_result.exit_code}") - - for line in exec_result.output[0].decode('utf-8').splitlines(): - print(f"{line}") - - assert exec_result.exit_code == 0, f"Key generation failed for {key_type}" - - print(f"testing for existence of key file at {key_path}id_{key_type}.pub") - exec_result = client_container.exec_run(cmd=f"ls -la {key_path}", demux=True) - print(f"File exists: {exec_result.exit_code}") - - if exec_result.output[0] is not None: - for line in exec_result.output[0].decode('utf-8').splitlines(): - print(f"{line}") - -@pytest.mark.parametrize("key_type", ["rsa", "dsa", "ecdsa", "ed25519"]) -def test_ssh_keys_deploy(client_container, server_container, key_type): - print("") #newline - - print(f"testing deployment of {key_type} key to {server_container.name}") - - cmd=f'''/bin/bash -c \"source /home/appuser/ssh-venv/bin/activate && /home/appuser/code/ssh/ssh-keys.py --generate {key_type} --deploy appuser@ssh-server --password_type dotenv\"''' - - print(f"running:") - print(f"{cmd}") - - exec_result = client_container.exec_run(cmd=cmd, demux=True) - print(f"exit code: {exec_result.exit_code}") - - for line in exec_result.output[0].decode('utf-8').splitlines(): - print(f"{line}") - - assert exec_result.exit_code == 0, "Key deployment failed" - # it would be good to test the connection without password, which is actually the final goal - # but that requires more setup with user input for accepting server key \ No newline at end of file From b92e78478671b13fcecf9616152c45f3e36cd959 Mon Sep 17 00:00:00 2001 From: Dietrich Wall Date: Sun, 13 Sep 2026 13:49:20 +0200 Subject: [PATCH 2/6] run pipeline on all branches --- .github/workflows/ci-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 5ef18fa..36c3e8a 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -2,7 +2,8 @@ name: CI on: push: - branches: [main, develop] + branches: + - '**' pull_request: branches: [main] From 0aab3a9d78bc96083becef4cba737761fe1a5cce Mon Sep 17 00:00:00 2001 From: Dietrich Wall Date: Sun, 13 Sep 2026 15:42:12 +0200 Subject: [PATCH 3/6] fix virtual environment setup --- .github/workflows/ci-build.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 36c3e8a..06bc44f 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -24,17 +24,24 @@ jobs: cache: 'pip' cache-dependency-path: 'pyproject.toml' - - name: Install dependencies + - name: Create virtual environment run: | + python -m venv .venv + + - name: Activate virtual environment and install dependencies + run: | + source .venv/bin/activate python -m pip install --upgrade pip pip install ".[dev]" - name: Run tests run: | + source .venv/bin/activate pytest tests/test_package.py - name: Build package run: | + source .venv/bin/activate pip install build python -m build @@ -45,4 +52,3 @@ jobs: path: dist/ retention-days: 7 if-no-files-found: ignore - # overwrite-mode: allow-existing From 3234880135ea525590e4d046bb4690471b088ead Mon Sep 17 00:00:00 2001 From: Dietrich Wall Date: Sun, 13 Sep 2026 16:46:50 +0200 Subject: [PATCH 4/6] added python package debugging environment --- .github/workflows/ci-build.yml | 15 ++++++++++++--- requirements.txt | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 06bc44f..41b7a27 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -28,16 +28,25 @@ jobs: run: | python -m venv .venv + - name: Activate virtual environment and install dependencies run: | source .venv/bin/activate python -m pip install --upgrade pip pip install ".[dev]" - + pip install -r requirements.txt + + - name: Debug Python environment + run: | + python --version + which python + python -m pip --version + python -m pip show build + python -c "import build; print(build); print(build.__file__)" + - name: Run tests run: | - source .venv/bin/activate - pytest tests/test_package.py + source .venv/bin/activate && pytest tests/test_package.py - name: Build package run: | diff --git a/requirements.txt b/requirements.txt index be71acd..271f300 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ # requirements for global installation paramiko>=3.4.0 +build \ No newline at end of file From 184d8bdca8fbdb814d884cc954e0f4f379cbeb19 Mon Sep 17 00:00:00 2001 From: Dietrich Wall Date: Sun, 13 Sep 2026 16:56:03 +0200 Subject: [PATCH 5/6] improved workflow - test python path --- .github/workflows/ci-build.yml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 41b7a27..0402436 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -27,14 +27,13 @@ jobs: - name: Create virtual environment run: | python -m venv .venv - - - - name: Activate virtual environment and install dependencies + echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH" + + - name: Install dependencies run: | - source .venv/bin/activate python -m pip install --upgrade pip - pip install ".[dev]" - pip install -r requirements.txt + python -m pip install ".[dev]" + python -m pip install -r requirements.txt - name: Debug Python environment run: | @@ -46,14 +45,12 @@ jobs: - name: Run tests run: | - source .venv/bin/activate && pytest tests/test_package.py - + pytest tests/test_package.py + - name: Build package run: | - source .venv/bin/activate - pip install build python -m build - + - name: Upload build artifacts uses: actions/upload-artifact@v4 with: From 491a65fc32ceb03f148c775127c1a68c504b1e4d Mon Sep 17 00:00:00 2001 From: Dietrich Wall Date: Sun, 13 Sep 2026 17:07:43 +0200 Subject: [PATCH 6/6] update actions add build to dependencies --- .github/workflows/ci-build.yml | 22 +++++----------------- pyproject.toml | 1 + 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 0402436..f5968bb 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -12,10 +12,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7.0.1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 @@ -24,25 +24,13 @@ jobs: cache: 'pip' cache-dependency-path: 'pyproject.toml' - - name: Create virtual environment - run: | - python -m venv .venv - echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH" - - - name: Install dependencies + - name: Install dependencies (setup-python provides pip in $PATH) run: | + python --version python -m pip install --upgrade pip python -m pip install ".[dev]" python -m pip install -r requirements.txt - - name: Debug Python environment - run: | - python --version - which python - python -m pip --version - python -m pip show build - python -c "import build; print(build); print(build.__file__)" - - name: Run tests run: | pytest tests/test_package.py @@ -52,7 +40,7 @@ jobs: python -m build - name: Upload build artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7.0.1 with: name: python-build-${{ matrix.python-version }} path: dist/ diff --git a/pyproject.toml b/pyproject.toml index 132d5a5..3748b63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ dev = [ "pytest-cov >= 4.0.0", "pytest-mock >= 3.12.0", "python-dotenv >= 1.0.0", + "build" ] [tool.setuptools.packages.find]