Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 0 additions & 28 deletions .github/actions/poetrybuild/action.yaml

This file was deleted.

35 changes: 35 additions & 0 deletions .github/actions/uvbuild/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: 2024 DB Systel GmbH
#
# SPDX-License-Identifier: Apache-2.0

name: "Reusable uv build workflow"
description: "Reusable workflow to set up Python and install dependencies via uv. The Python version and additional uv arguments can be configured via inputs."
inputs:
python:
default: "3.14"
description: "Value for 'python-version'"
required: false
type: string
uv_args:
default: ""
description: "Additional arguments for the uv install step'"
required: false
type: string
runs:
using: "composite"
steps:
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ inputs.python }}
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
- name: Setup project
run: |
uv_args="${UV_ARGS}"
uv sync --locked --all-extras --dev $uv_args
env:
UV_ARGS: ${{ inputs.uv_args }}
shell: bash
23 changes: 18 additions & 5 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,30 @@
# SPDX-License-Identifier: Apache-2.0

name: Publish release on PyPI

on:
release:
types: [published]

jobs:
build:
pypi-publish:
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/github-org-manager
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build and publish to PyPI
uses: JRubics/poetry-publish@4b3306307f536bbfcb559603629b3b4f6aef5ab8 # v2.1
with:
pypi_token: ${{ secrets.PYPI_TOKEN }}
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: false # avoid cache-poisoning attacks
- name: Build package
run: uv build
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
15 changes: 10 additions & 5 deletions .github/workflows/real-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ on:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
selftest:
runs-on: ubuntu-24.04
Expand All @@ -16,7 +19,9 @@ jobs:
GITHUB_APP_PRIVATE_KEY: ${{ secrets.TEST_GITHUB_APP_PRIVATE_KEY }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/poetrybuild
with:
persist-credentials: false
- uses: ./.github/actions/uvbuild
- name: Replace data in config files
run: |
sed -i "s/TEST_GITHUB_ORG/${{ secrets.TEST_GITHUB_ORG }}/g" tests/data/config/org_files/*.yaml
Expand All @@ -28,14 +33,14 @@ jobs:
cp tests/data/config/teams_files/teams_changes.yaml tests/data/config/teams/teams.yaml
cp tests/data/config/org_files/org_changes.yaml tests/data/config/org.yaml
- name: Run 1 (changes) - dry run
run: poetry run gh-org-mgr sync -c tests/data/config/ --dry -vv
run: uv run gh-org-mgr sync -c tests/data/config/ --dry -vv
- name: Run 1 (changes) - prod run
run: poetry run gh-org-mgr sync -c tests/data/config/ -vv
run: uv run gh-org-mgr sync -c tests/data/config/ -vv
- name: Prepare for second run, reverting to original state
run: |
cp tests/data/config/teams_files/teams_orig.yaml tests/data/config/teams/teams.yaml
cp tests/data/config/org_files/org_orig.yaml tests/data/config/org.yaml
- name: Run 2 (revert) - dry run
run: poetry run gh-org-mgr sync -c tests/data/config/ --dry -vv
run: uv run gh-org-mgr sync -c tests/data/config/ --dry -vv
- name: Run 2 (revert) - prod run
run: poetry run gh-org-mgr sync -c tests/data/config/ -vv
run: uv run gh-org-mgr sync -c tests/data/config/ -vv
3 changes: 3 additions & 0 deletions .github/workflows/release-vulnerabilities.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ on:
schedule:
- cron: "35 9 * * 1" # run a check once a week

permissions:
contents: read

jobs:
osv-check:
runs-on: ubuntu-latest
Expand Down
60 changes: 32 additions & 28 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ on:
- main
pull_request:

permissions:
contents: read

jobs:
# Test using the tool via poetry on different OSes and python versions
# Test using the tool via uv on different OSes and python versions
test-os-python-matrix:
runs-on: ${{ matrix.os }}
strategy:
max-parallel: 10
# do not abort the whole test job if one combination in the matrix fails
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
os: [ubuntu-22.04]
include:
- python-version: "3.10"
Expand All @@ -29,64 +32,65 @@ jobs:

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/poetrybuild
with:
persist-credentials: false
- uses: ./.github/actions/uvbuild
with:
python: ${{ matrix.python-version }}
poetry_args: --only main
uv_args: --no-dev
- name: Execute gh-org-mgr
run: poetry run gh-org-mgr --help
run: uv run gh-org-mgr --help

# Test building the package and installing it via pip3
test-build-install:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.14"
- name: Install poetry
run: pip install poetry
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
- name: Build package
run: poetry build
run: uv build
- name: Install package
run: pip3 install dist/github_org_manager-*.tar.gz
- name: Run package
run: |
gh-org-mgr --version
gh-org-mgr --help

# Formatting
pylint:
# Quality checks
ruff:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/poetrybuild
- name: Lint with pylint
run: poetry run pylint --disable=fixme gh_org_mgr/

formatting:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/poetrybuild
- name: Test formatting with isort and black
run: |
poetry run isort --check gh_org_mgr/
poetry run black --check .

mypy:
with:
persist-credentials: false
- uses: ./.github/actions/uvbuild
- name: Lint with ruff
run: uv run ruff check
ty:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/poetrybuild
- name: Test typing with mypy
run: poetry run mypy
with:
persist-credentials: false
- uses: ./.github/actions/uvbuild
- name: Test typing with ty
run: uv run ty check

# REUSE
reuse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Check REUSE Compliance
uses: fsfe/reuse-action@676e2d560c9a403aa252096d99fcab3e1132b0f5 # v6.0.0
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ This Project welcomes contributions, suggestions, and feedback. All contribution

## Development setup

Starting development is as easy as installing Python `poetry` and running `poetry install` once.
Starting development is as easy as installing [uv](https://docs.astral.sh/uv/) and running `uv sync` once.

In order to run the project in the new virtual environment, run `poetry run gh-org-mgr`.
In order to run the project in the new virtual environment, run `uv run gh-org-mgr`.

---
Based on [GitHub's Minimum Viable Governance (MVG)](https://github.com/github/MVG). Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by/4.0/).
2 changes: 1 addition & 1 deletion gh_org_mgr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: Apache-2.0

"""Global init file"""
"""Global init file."""

from importlib.metadata import version

Expand Down
39 changes: 20 additions & 19 deletions gh_org_mgr/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
#
# SPDX-License-Identifier: Apache-2.0

"""Handling the private and organisation configuration"""
"""Handling the private and organisation configuration."""

import logging
import os
import re
import sys
from pathlib import Path
from typing import Any

import yaml
Expand Down Expand Up @@ -100,38 +100,38 @@


def _find_matching_files(directory: str, pattern: str, only_one: bool = False) -> list[str]:
"""
Get all files in a directory matching a regex pattern.
"""Get all files in a directory matching a regex pattern.

Args:
- directory: Path to the directory
- pattern: Regular expression pattern to match filenames
- only_one: Whether only the first match shall be returned.
directory: Path to the directory.
pattern: Regular expression pattern to match filenames.
only_one: Whether only the first match shall be returned.

Returns:
- List of filenames matching the pattern
List of filenames matching the pattern.
"""
matching_files: list[str] = []

dir_path = Path(directory)

# Validate directory existence
if not os.path.isdir(directory):
if not dir_path.is_dir():
logging.error("'%s' is not a valid directory", directory)

else:
# Compile the regex pattern
regex_pattern = re.compile(pattern + "$")

# Traverse the directory and find matching files
for file_name in os.listdir(directory):
if regex_pattern.match(file_name):
file_path = os.path.join(directory, file_name)
if os.path.isfile(file_path):
matching_files.append(file_path)
for entry in dir_path.iterdir():
if regex_pattern.match(entry.name):
if entry.is_file():
matching_files.append(str(entry))
else:
logging.warning(
"'%s' looks like a file we searched for, but it's not. "
"Will not consider its contents",
file_path,
entry,
)

if only_one and len(matching_files) > 1:
Expand All @@ -154,7 +154,7 @@ def _find_matching_files(directory: str, pattern: str, only_one: bool = False) -


def _read_config_file(file: str) -> dict:
"""Return dict of a YAML file"""
"""Return dict of a YAML file."""
logging.debug("Attempting to parse YAML file %s", file)
with open(file, encoding="UTF-8") as yamlfile:
config: dict = yaml.safe_load(yamlfile)
Expand All @@ -166,7 +166,7 @@ def _read_config_file(file: str) -> dict:


def _validate_config_schema(file: str, cfg: dict, schema: dict) -> None:
"""Validate the config against a JSON schema"""
"""Validate the config against a JSON schema."""
try:
validate(instance=cfg, schema=schema, format_checker=FormatChecker())
except ValidationError as e:
Expand All @@ -177,11 +177,12 @@ def _validate_config_schema(file: str, cfg: dict, schema: dict) -> None:

def parse_config_files(path: str) -> tuple[dict[str, str | dict[str, str]], dict, dict]:
"""Parse all relevant files in the configuration directory. Returns a tuple
of org config, app config, and merged teams config"""
of org config, app config, and merged teams config.
"""
# Find the relevant config files for app, org, and teams
cfg_app_files = _find_matching_files(path, APP_CONFIG_FILE, only_one=True)
cfg_org_files = _find_matching_files(path, ORG_CONFIG_FILE, only_one=True)
cfg_teams_files = _find_matching_files(os.path.join(path, TEAM_CONFIG_DIR), TEAM_CONFIG_FILES)
cfg_teams_files = _find_matching_files(str(Path(path) / TEAM_CONFIG_DIR), TEAM_CONFIG_FILES)

# Read and parse config files for app and org
cfg_app = _read_config_file(cfg_app_files[0])
Expand Down
Loading