pyinfra-dev / pyinfra

pyinfra turns Python code into shell commands and runs them on your servers. Execute ad-hoc commands and write declarative operations. Target SSH servers, local machine and Docker containers. Fast and scales from one server to thousands.
https://pyinfra.com
MIT License
3.93k stars 383 forks source link

[APT] Add policy/preference-related facts/operations/options #666

Open gchazot opened 3 years ago

gchazot commented 3 years ago

Is your feature request related to a problem? Please describe

In some cases it is useful to force or ban specific versions of packages from being installed, at the initial installation step, as well as in downstream upgrade steps. This can be managed through apt-cache policy (aka "preferences") features.

These cases range from compatibility with the application being run to excluding buggy/unsafe versions of the software or even completely forbidding some package to be installed.

Those settings are intended to last beyond the initial installation of the package, through the entire lifetime of the target system.

Describe the solution you'd like

In a pyinfra deploy, I'd like to be able to describe these policies with operations and let apt.packages and apt.upgrade naturally respect them.

Main interface

Ideas for the format this could take:

apt.policy(
    name="Pin version of python",
    packages="python3.8",
    pin={
        "version": "3.8.5*",
        "distrib": "stable",
        "origin": "Debian",
     },
    priority=1001,
)

With careful defaults and maybe a couple constants fora readability, this could be much shorter for simple use cases. For example, the same would be:

apt.policy(
    name="Pin version of python",
    packages="python3.8",
    pin={"version": "3.8.5*"},  # Default both origin and release to being "*" (== unset)
    priority=apt.policy.ALWAYS,  # This would be the default value, just adding here to highlight constant usage
)

Integration in apt.packages

In addition, this could be combined into the apt.packages operation. The example above would be achieved with:

apt.packages(
    name="Install Python 3.8.5",
    packages="python3.8=3.8.5*",
    policy_pin=apt.policy.ALWAYS,
)
Fizzadar commented 3 years ago

Hey @gchazot! I like this idea a lot, apt.policy would be a great addition to the module.

This could be implemented relatively painlessly in the same way as [yum|dnf].repo where they create an in-memory file for the repo and then use files.put to ensure it exists (https://github.com/Fizzadar/pyinfra/blob/current/pyinfra/operations/util/packaging.py#L236-L256).

So for your Python example it'd just need to create a file like so:

# /etc/apt/preferences.d/X
Package: python3.8
Pin: version=3.8.5*
Pin-Priority: 1001

The hard bit is what filename to use :)

The secondary part could be more complex - would probably have to split the version, and error(?) if there's no version.. More to think about for that part...