Skip to content

Commit

Permalink
Merge branch 'migrate_to_pydantic' into make-testsuite-work-again-202405
Browse files Browse the repository at this point in the history
  • Loading branch information
pederhan authored Jun 24, 2024
2 parents 071012f + 7485ec1 commit 8542420
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 12 deletions.
124 changes: 124 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
name: build mreg-cli

on:
push:
tags:
- mreg-cli-v*

concurrency:
group: build-mreg-cli-${{ github.head_ref }}

jobs:
build_pypi:
name: Build wheels and source distribution
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Exit if not on master branch
if: github.ref_name != 'master'
run: exit -1

- name: Install build dependencies
run: python -m pip install --upgrade build

- name: Build source distribution
run: python -m build

- uses: actions/upload-artifact@v4
with:
name: pypi_artifacts
path: dist/*
if-no-files-found: error

build_pyinstaller:
name: Build pyinstaller binary
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-22.04, windows-latest, macos-latest]
python-version:
- '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 }}

- name: Run PyInstaller with Tox
run: |
python -m ensurepip --upgrade
python -m pip install tox tox-uv tox-gh-actions
tox
- name: Rename binary
run: |
mv dist/mreg-cli${{ contains(matrix.os, 'windows') && '.exe' || '' }} dist/mreg-cli-${{ matrix.os }}-${{ matrix.python-version }}${{ contains(matrix.os, 'windows') && '.exe' || '' }}
- uses: actions/upload-artifact@v4
with:
name: mreg-cli-${{ matrix.os }}-${{ matrix.python-version }}${{ contains(matrix.os, 'windows') && '.exe' || '' }}
path: dist/mreg-cli-${{ matrix.os }}-${{ matrix.python-version }}${{ contains(matrix.os, 'windows') && '.exe' || '' }}
if-no-files-found: error

publish_pypi:
name: Publish PyPI release
needs:
- build_pypi
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: pypi_artifacts
path: dist

- name: Push build artifacts to PyPI
uses: pypa/[email protected]

publish_github:
name: Publish GitHub release
needs:
- build_pypi
- build_pyinstaller
runs-on: ubuntu-latest

steps:
- name: Download PyInstaller binaries
uses: actions/download-artifact@v4
with:
pattern: mreg-cli-*
path: dist
merge-multiple: true

- name: Download wheel and source distributions
uses: actions/download-artifact@v4
with:
pattern: pypi_artifacts
path: dist
merge-multiple: true

- name: Create GitHub release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: |
Release notes for ${{ github.ref }}
draft: false
prerelease: false

- name: Upload release asset
id: upload-release-asset
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: dist/*
5 changes: 1 addition & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ jobs:
- name: Install mreg-cli
run: |
python -m ensurepip --upgrade
pip install -r requirements.txt
pip install -e .[dev]
- name: Test and compare api calls
run: ci/run_testsuite_and_record.sh
Expand All @@ -57,9 +56,7 @@ jobs:
- name: Install dependencies
run: |
python -m ensurepip --upgrade
python -m pip install tox tox-gh-actions
python -m pip install -r requirements.txt
python -m pip install -r requirements-dev.txt
python -m pip install tox tox-uv tox-gh-actions
- name: Test with tox
run: tox r

12 changes: 12 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Dmytro Karpenko
Fredrik Larsen
Magnus Hirth
Marius Bakke
Nico
Nils Hiorth
Paal Braathen
Peder Hovdan Andresen
Safet Amedov
Terje Kvernes
Øyvind Hagberg
Øyvind Kolbu
36 changes: 36 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

The big Pydantic update. The entire codebase has been rewritten to use Pydantic for request and response validation. This brings with it a huge improvement to the development experience and the robustness of the code.

### Added

- `--version` option to display current application version and exit.
Version number is in the form of `major.minor.patch` if installed from a published version.
Shows a version number including the commit hash if installed from a git repository in the form of `major.minor.patch.dev123+gabc1234`.
See [Default versioning scheme](https://setuptools-scm.readthedocs.io/en/latest/usage/#default-versioning-scheme) in the [setuptools_scm](https://github.com/pypa/setuptools_scm/) documentation for more information.
- The version can be accessed programmatically with `mreg_cli.__version__`.
- `label set_description` command to set the description of a label.
- `network list_excluded_ranges` command to list the excluded ranges of a network.
- Application can now store tokens for multiple servers and will pick the correct one based on the server URL.
- Building binaries for Windows, Linux and MacOS, and publishing the package to PyPI on each GitHub release.

### Changed

- The application now uses Pydantic internally to validate request and response data. This should make the code more robust and easier to maintain.
- Application now attempts to send JSON for every request. This should improve the consistency of the API responses.
- Version now follows Semantic Versioning 2.0.0 and is automatically determined based on the most recent git tag (`mreg-cli-v*`). As part of this change, the verison has been bumped from 0.9.10 to 1.0.0. See the Added section for more information on how version numbers are accessed.

### Removed

- `label rename -desc` option. Description modification is now done through the new `label set_description` command.

### Fixed

- Hopefully more than we broke.
32 changes: 32 additions & 0 deletions CONTRIBUTING
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Contributing

If you would like to contribute to this project, please follow these steps:

1. Fork the repository
2. Create a new branch (`git checkout -b feature/branch-name`)
3. Make changes
4. Commit your changes (`git commit -am 'Add some feature'`)
5. Push to the branch (`git push origin feature/branch-name`)
6. Create a Pull Request

## Publishing

Publishing new versions is automatically handled by GitHub actions for versions tagged with `mreg-cli-v*` (e.g. `mreg-cli-v1.0.0`). If you are a maintainer, you can create a new release by following these steps:

1. Switch to the `master` branch:

```bash
git checkout master
```

2. Create a new tag:

```bash
git tag mreg-cli-v1.2.3
```

3. Push the tag to the upstream repository:

```bash
git push upstream mreg-cli-v1.2.3
```
4 changes: 4 additions & 0 deletions mreg_cli/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,10 @@ class Role(HostPolicy, WithHistory):

history_resource: ClassVar[HistoryResource] = HistoryResource.HostPolicy_Role

def __hash__(self) -> int:
"""Hash the role by ID and name."""
return hash(str(self.id) + self.name)

@classmethod
def endpoint(cls) -> Endpoint:
"""Return the endpoint for the class."""
Expand Down
13 changes: 13 additions & 0 deletions mreg_cli/commands/host_submodules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,16 @@
Note: This design will imply circular imports, so the core host command
imports the submodules after it has been initialized.
"""

# We have to import each of the submodules explictly here in order to
# ensure they are included when we build binaries with PyInstaller.
# We also cannot do `from . import *`. Each module must be specified.
from . import a_aaaa, bacnet, cname, core, rr

__all__ = [
"a_aaaa",
"bacnet",
"cname",
"core",
"rr",
]
33 changes: 33 additions & 0 deletions mreg_cli/commands/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,39 @@ def host_add(args: argparse.Namespace) -> None:
OutputManager().add_ok(f"Added host {host.name!r} to role {role_name!r}")


@command_registry.register_command(
prog="host_copy",
description="Copy roles from one host to another",
short_desc="Copy roles between hosts",
flags=[
Flag("source", description="Source host", metavar="SOURCE"),
Flag("destination", description="Destination host", nargs="+", metavar="DESTINATION"),
],
)
def host_copy(args: argparse.Namespace) -> None:
"""Copy roles from one host to another.
:param args: argparse.Namespace (source, destination)
"""
source_name: str = args.source
source = Host.get_by_any_means_or_raise(source_name)
source_roles = set(source.roles())

for destination_name in args.destination:
destination = Host.get_by_any_means_or_raise(destination_name)
destination_roles = set(destination.roles())
OutputManager().add_line(f"Copying roles from from {source_name} to {destination_name}")

# Check if role already exists in destination
for role in source_roles & destination_roles:
OutputManager().add_line(f" + {role.name} (existing membership)")

# Check what roles need to be added
for role in source_roles - destination_roles:
role.add_host(destination.name.hostname)
OutputManager().add_line(f" + {role.name}")


@command_registry.register_command(
prog="host_list",
description="List roles for host(s)",
Expand Down
6 changes: 3 additions & 3 deletions mreg_cli/utilities/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@

T = TypeVar("T")

JsonMappingValidator = TypeAdapter(JsonMapping)


def error(msg: str | Exception, code: int = os.EX_UNAVAILABLE) -> NoReturn:
"""Print an error message and exits with the given code."""
Expand Down Expand Up @@ -398,10 +400,8 @@ def get_list_unique(
ret = get_list_generic(path, params, ok404, expect_one_result=True)
if not ret:
return None

try:
validator = TypeAdapter(JsonMapping)
return validator.validate_python(ret)
return JsonMappingValidator.validate_python(ret)
except ValueError as e:
raise ValidationError(f"Failed to validate response from {path}: {e}") from e

Expand Down
22 changes: 21 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ build-backend = "setuptools.build_meta"
name = "mreg-cli"
description = "MREG interactive command line client"
readme = "README.md"
maintainers = [
# Active maintainers
{ name = "Terje Kvernes", email = "[email protected]" },
{ name = "Øyvind Hagberg", email = "[email protected]" },
{ name = "Peder Hovdan Andresen", email = "[email protected]" },
]
authors = [
# Historical authors
{ name = "Øyvind Kolbu", email = "[email protected]" },
{ name = "Safet A", email = "[email protected]" },
{ name = "Magnus Hirth", email = "[email protected]" },
{ name = "Nico", email = "[email protected]" },
{ name = "Marius Bakke", email = "[email protected]" },
{ name = "Fredrik Larsen", email = "[email protected]" },
{ name = "Paal Braathen", email = "[email protected]" },
{ name = "Nils Hiorth", email = "[email protected]" },
{ name = "Dmytro Karpenko", email = "[email protected]" },
{ name = "Others (see AUTHORS)" },
]

requires-python = ">=3.11"
license = { file = "LICENSE" }
keywords = ["mreg", "cli", "interactive"]
Expand All @@ -29,7 +49,7 @@ dependencies = [
dynamic = ["version"]

[project.optional-dependencies]
dev = ["ruff", "tox", "pyinstaller", "rich"]
dev = ["ruff", "tox", "rich"]

[project.urls]
Repository = 'https://github.com/unioslo/mreg-cli/'
Expand Down
11 changes: 7 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ commands =

[testenv]
skip_install = false
description = Test building binary with pyinstaller
description = Build binary with pyinstaller for {basepython}
basepython =
python311: python3.11
python312: python3.12
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/requirements-dev.txt
3.11: python3.11
3.12: python3.12
extras =
dev
deps =
pyinstaller
allowlist_externals =
pyinstaller
commands =
Expand Down

0 comments on commit 8542420

Please sign in to comment.