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

Migrate to pydantic #214

Merged
merged 99 commits into from
Oct 8, 2024
Merged

Migrate to pydantic #214

merged 99 commits into from
Oct 8, 2024

Conversation

terjekv
Copy link
Collaborator

@terjekv terjekv commented Apr 9, 2024

Another large scale rewrite to move away from a bundle of dicts into something actually structured into Pydantic models. The goal here is to be able to add features and output data without needing to test, guess, and generally fiddle with dicts everywhere...

  - Also makes get_list do the logic of checking hit limits and exception handling.
  - Only the API module has any idea of endpoint addresses for pydantic calls. The rest of the client code does not care.
  - Implements a IPAddressField for Hosts to reduce the number of str-fields we have. We should ideally have none.
@terjekv terjekv self-assigned this Apr 9, 2024
terjekv and others added 8 commits April 9, 2024 15:07
* Clean up authentication mess.

  - Cleans up the names for the auth functions.
  - Adds a LoginFailedError exception.
  - Passes on params from the pre-relogin command to the post-login command.
  - Reintroduces the previous behaviour where failing the login upon startup exists mreg.
* Allow oneshot commands from the terminal.

This patch allows the use of mreg-cli without interaction, ala:

`mreg-cli host info foo`

This will then feed the command `host info foo` to the cli and then exit.

Also adds an option to force token only login. Good for scripts. :)
- No micromanagment of error types, just dump the error text back to the user.
## The problem...

It was much too easy to do `host remove foo -f` and have it bite in the worst possible way.

## The solution?

In this PR, `-force` is no longer a catch-all for making `host remove`, well, remove the host. If the host has cnames, ptrs, mx, srvs or ipadresses across multiple vlans, the user needs to declare a desire to override each of these explicitly via the new option `-override`.

## Supported input

Accepted overrides are `cname`, `ipadress`, `mx`, `srv`, `ptr`, `naptr`. Invalid overrides offered as parameters to `-override` are errors and the command will report on the unexpected input and stop before executing anything.

The choice to have these as textual inputs is intended with the explicit goal of making their use require more than tab-completing an option.

## Example usage

`host remove foo -force -override cname,ipaddress,mx`.

This would allow deletion / removal of a host with cnames, ipadresses across different vlans, and mx set. However, any ptr or srv RRs will still cause the deletion to cancel.

## Example warning from `host remove`

```
WARNING: : bar.example.org requires force and cnames as overrides for deletion:
  1 cnames, override with 'cname'
    - fubar.example.org
  multiple ipaddresses on the same VLAN. Must use 'force'."
```

## Notes:

1. `-force` alone works on multiple ipadresses from the same VLAN.
2. `-override` requires the presence of `-force`. The documentation states this both from expanded inline help and `host remove -h`.


---------

Co-authored-by: pederhan <pederhan@usit.uio.no>
@pederhan
Copy link
Member

pederhan commented Apr 9, 2024

I've used Pydantic for a few CLI projects by now, and there are a couple of points I think you should have in the back of your mind when developing this further:

  1. How will you handle data that fails validation? When fetching all hosts, should a single failing host break the entire host fetching functionality of the application, or will you implement some sort of best-effort fetching?
  2. When validation fails, how do you notify the user about it? Do you leak the entire stack trace, show just the Pydantic ValidationError message, or write some custom exception message or handler that hides it from the user entirely?

Regarding point 2, it's important to address this IMO, as the ValidationError exceptions provide comprehensive information about what and why validation failed, but it's not really something the user can act on, so to them it's kind of meaningless noise.

FWIW, I don't have good answers to these questions. You want to balance reliability and ease of use with correctness and debuggability, and those factors can be at odds with each other at times.

@terjekv
Copy link
Collaborator Author

terjekv commented Apr 9, 2024

Yeah, I've been down the same thoughts myself. Tbh, these kinds of problems are why I'm looking at gRPC for other projects, and also why they tend to come in Rust. Which doesn't really help the here and now. :p

terjekv and others added 16 commits April 9, 2024 22:08
  - Also differentiates between get_list and get_list_unique where one expects many or one hit.
  - Further improve models with better cleaning of macaddress.
  - Avoid string literals for endpoints, use an Enum with optional identifier application.
  - api.get_host now allows for lookups by id, ipaddress, macaddress or hostname, and if all that fails checks for CNAMEs.
  - Add network endpoints and more.
  - Make models frozen so they can be used as hash keys, returning new objects of one tries to assign to them via acceptable methods. __setattr__ and __delattr__ are overridden to cause AttributeErrors if used.
  -  Migrate `host add` and `host remove`
  - Document (some) of the older API calls with exceptions.
  - Host output now uses class methods from the models involved to print multiple entries.
  - clean_hostname is copied around a bit to support old and new code... :(
  - Models with a host parameter also get a method to resolve the host.
  - Added support for created_at and updated_at.
  - We don't use clean_hostname, we have a hostname type.
  - Make roles look sane for the consumer of the cli.
  - Models know their endpoint
  - This allows for class.get, class.create, obj.delete, obj.refetch, obj.patch
  - patch/create return the new obj
  - Needs refactoring of model file, it's getting large.
  - This does not rely on getting the IP from the server first, it uses the servers API directly.
mreg_cli/utilities/api.py Outdated Show resolved Hide resolved
pederhan and others added 10 commits May 29, 2024 14:02
* Validate responses with Pydantic

* Specify part of statement `type: ignore` applies to

* Replace all `get_list` calls with `get_typed`

* Use `TypeAliasType` from typing-extensions

* Revert accidental find and replace change

* HACK: Use `cast()` in `get_list_unique()`

* Add source url comment to recursive JSON type

* Add helper functions for response validation

* Remove `ResponseLike`

* Raise MREG ValidationError on failed validation

* Validate response in `get_list_unique`

* Improve exception handling for failed validation

* Add `HistoryResource` from #232

Also directly validates results to `HistoryItem` instead of going through a dict.
* Strip None values when sending JSON

* Recursively strip
* Require SOCKS dependencies

* Add pyinstaller dev dependency
* Print git commit in `--version`

* Use versioneer to determine git revision

* Use `setuptools_scm`

* Fix scm version

* Fully integrate dynamic versioning

* Delete versioneer-created gitattributes file
- This PR allows for copying of all policies (roles) from one host to another via a new command, `policy host_copy`.
* Support multiple destinations in host_copy.

  - Also reduces output noise.
  - Also handles the situation where a destination already has the role in question.
  - Requires Role to be hashable.
pederhan and others added 9 commits June 12, 2024 11:00
* Publishing

* Add branch condition to publish workflow

* Wording

* Remove usage of requirements.txt files

* Add basepython resolution for numbered envs

* Use uv in tox workflows

* Unfinished publish workflow

* trying stuff

* Do not specify tox env in build workflow

* Move pyinstaller dependency to tox

* Actually run tox

* Fix binary dist path

* Revert incorrect binary path

* Fix underscore in artifact name

* verbose pypi

* Use test pypi

* Use `mreg-cli-v*` tag pattern

* Dump workflow info

* Add branch check

* Try to create release

* Try to use trusted publishing

* Remove unused pypi vars

* Try to rename binaries

* Try to actually read actions docs

* Fix bin  name

* Publish a4

* Rename binary after creation

* Remove publishing branch

* Make built binary executable

* Remove executable step

* Build on master and publish to real pypi

* Update publishing instructions

* Exit explicitly only on master branch

* Add artifact and version info, move `-desc`to Removed

* Final publishing test run

* Enable master protection again

* Make GitHub release non-prelease

* Add missing newline

* Fix CONTRIBUTING markdown lint violations

Does not "fix" the ordered list rule violation, since indenting the code blocks looks very awkward.
* Add authors to pyproject.toml

* Add historical authors

* Add AUTHORS file
* Make the cli exceptions write to outputmanager

* Replace formatted datetime strings in output

* Fix workflow annotations about GH action versions

* Update testsuite json with modified label commands

* Workaround for json-incapable server

* Also use workaround for patch requests

* Use OutputManager instead of the cli_* methods

* Use specialized exceptions. (#256)

- This PR migrates all use of CliError and CliWarning to more specialized exceptions.
  - Also fix a bug with handling broken filter expressions.
  - Prefixes Errors with ERROR as not everyone has color vision.

* Cull utility functions that aren't used anymore

* Further culling of... old stuff.

* Fix valid_numeric_ttl min and max value handling

* Use `APIMixin.patch()` in `Zone.set_default_ttl()`

* Add missing OK message for `host add`

* Remove IP/network from `host add` OK message

* Verified behaviour for all zone commands

I have looked at the new output and api calls for the "zone" commands in
the testsuite and it looks good to me. Updating testsuite-result.json to
reflect that.

* Verified behaviour for all group commands

I have looked at the new output and api calls for the "group" commands
in the testsuite and it looks good to me.
Fixed a bug in history.py concerning how history is printed when owners
or hosts are added/removed from hostgroups.
Made a minor change to the datetime regex in diff.py to handle the extra
space that comes before a single digit date.

* Fix fetching network from location header (#258)

* Extract fetching resource by location to separate method

Fixes failing `network create` tests

* Use `field_for_endpoint()`

* Prefer using location header verbatim

Adds handling for endpoints that return invalid location headers.

* Remove `field_for_endpoint()`

* Remove placeholder comment, move src comments

* Remove special label handling, never re-fetch labels

* Add a check for frozen/force to "host add"

We forgot to check if the network was frozen in "host add". Added a
check for that in this commit, but we'll probably have to do
something similar in a_aaaa.py.

Went through more command output and verified it. Mostly network
commands this time.

Modified diff.py to not care about length of white space. Since ip
addresses can vary between runs, formatted output can vary in length,
spesifically how much whitespace is added.

* Fix fetching paginated endpoints twice in `get_list_generic` (#261)

* Fix fetching paginated endpoints twice in `get_list_generic`

* Break instead of return

* Refactor `get_list_generic` result checking

* Fix network excluded range output.

  - Also use IPAddressField for ips in excluded ranges.

* Fix delegeation response.

  - Add support for "delegation" scope akin to "zone" scope in server responses for zones.

* Implement logging. (#266)

* Implement logging.

  - Migrates out logging and recording commands to their own command files.
  - During the logging setup there was an issue trying to untangle commands and conventions, so most commands are now refactored into proper command files.
  - Add logging to API calls and output.
  - Move CliExit to exceptions.py...
  - When logging API calls, also logs the clients corrolation ID (for requests and respones) and the servers request ID (per request/response) for easier server side debugging. :)
  - Restrict logging (to errors only) when running the test suite, it's... noisy. :D

---------

Co-authored-by: Peder Hovdan Andresen <107681714+pederhan@users.noreply.github.com>

* Support sending more than str to post. (#264)

* Support sending more than str to post.

Also fix some ruffs.

* Consistent signatures for get_list_generic.

---------

Co-authored-by: Peder Hovdan Andresen <107681714+pederhan@users.noreply.github.com>

* Fix a bug in remove_excluded_range

models.Network.remove_excluded_range had a bug where it wouldn't match
the supplied parameters even when a range existed.
Solved by converting the ip address values to strings.

This commit also updates the rest of the "network" part of the test
suite results.

* Add network set_category/set_location commands

* Escape error messages (#269)

* Escape exception messages in HTML text

* Only escape message, not formatted exc

* Escape messages only in formatted exceptions (#271)

* Escape messages only in formatted exceptions

* Rename `escaped()` to `escape()`

* Add `QueryParams` type (#273)

* Add `QueryParams` type

* Remove redundant dict comprehension

* Add interactive diffing (#270)

Revert "Use dumb terminal only for diff.py"

This reverts commit 42a70de.

Use dumb terminal only for diff.py

Remove vertical borders for easier copying

Add comment about review mode

Invert conditional

Docstrings, ordering

Comments, names, docstrings

Don't review in CI

Change choices (order + choice)

Remove unused state

Revert "Run diff.py on testsuite results"

This reverts commit def4bc5.

Run diff.py on testsuite results

Refactor diff.py

Add interactive diffing

* Add force req for frozen nets to a_add, aaaa_add

Also:
- Add "rich" to requirements-dev.txt, it was missing
- Update testsuite-result.json with more verified test output

* Fix problems with history output

Also, update more test resuls

* Add minor quality of life + message changes to diff.py (#275)

* Remove `getattr()` usage in `_request_wrapper()`. (#274)

* Remove LoginFailedError message hard coding. (#277)

* Remove hardcoded LoginFailed output.

Fixes #272

* Fix quote usage and handle direction (im)possibly being unassigned. (#279)

* Add a regex for macaddress to diff.py

Macaddresses were caught by the IPv6 regex, which made certain commands
in the testsuite look like the wrong type of parameter was supplied.

* Add OK message to a_add when success

Also, avoid refetching the host twice.

* Remove unnecessary warning from dhcp assoc

dhcp assoc <hostname> <ip> would warn that hostname isn't a valid ip
address. This commit changes the code so the exception isn't raised but
otherwise it works the same.

Also:
- A minor change to an error message about a host having multiple ips.
- Verified the output from the rest of the commands in the dhcp section.

* Remove stripping of None values from PATCH calls (#282)

Also ensures nullable and non-nullable TTL fields are treated differently.

* Improve diff numbering + messages (#284)

* Add number to diff when reviewing

* Improve diff messages

* Handle `DiffError`

* Add `ERROR` msg prefix for exceptions

* Fix inconsistent stdout/stderr printing

* Remove parenthesis around plural s

* Fix `host set_comment` with empty string (#283)

* Use Pydantic v2 style model config in `FrozenModel` (#285)

* Use object repr when PATCH validation fails

* Improve type safety of validators (#286)

* Improve type safety of validators

* Remove unused import

* Improve safety of TTL methods for non-nullable fields (#287)

* Improve safety of TTL methods for non-nullable fields

* Add external id field for `Endpoint.Nameservers`

* Add QueryParam annotations for inline params

* Replace IDs & IPs in test result URLs (#288)

* Replace IDs & IPs in test result URLs

* Simplify `unquote_url`

* Expand match group explanations

* Fix comment typo

* A few code changes + changes to testsuite-result (#289)

- Changes to testsuite-result.json that came from #288
- The rest of the changes to testsuite-result.json, manually verified
- a_aaaa.py: Add a message and a requirement for force when adding an additional IP address to a host.
- rr.py: Replace occurrences of str(host.id) with host.id in API requests.

* Write back original lines in diff review (#290)

* Write back non-placeholder lines in diff reviews

* Refactor results to classes

* Rename `TestSuiteLog` to `TestSuiteResult`

* Delete .pyi file

* Fix missing None check

* Accept int values in queries

* Add missing annotations import

* Use uv in CI (#292)

* Use uv in CI

* Use official uv action

* Add build dependencies to pyproject.toml

* Use uv run?

* Set up python with uv in CI

* idk

* Use venv

* uv run?

* Use uv python when building too

* Remove system python env var

---------

Co-authored-by: pederhan <pederhan@usit.uio.no>
Co-authored-by: Terje Kvernes <terjekv@users.noreply.github.com>
Co-authored-by: Terje Kvernes <terje@kvernes.no>
Co-authored-by: Peder Hovdan Andresen <107681714+pederhan@users.noreply.github.com>
* Build PyInstaller binary in CentOS 8 container

* Use single release step
@terjekv terjekv marked this pull request as ready for review October 2, 2024 16:22
* Fix cli.py type checking errors

* Fix comment

* Explain noqa Ruff codes
* Add error msg builder with underlined suggestions

* Refactor error message building

* Add docstrings
Copy link
Member

@pederhan pederhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (again) 🤠

@pederhan pederhan marked this pull request as draft October 8, 2024 10:34
@pederhan pederhan marked this pull request as ready for review October 8, 2024 10:34
@pederhan pederhan merged commit 07e070e into master Oct 8, 2024
8 checks passed
@pederhan pederhan deleted the migrate_to_pydantic branch October 8, 2024 10:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants