Skip to content

Commit

Permalink
Ensure Salt and Python versions always follow supported ones (at least)
Browse files Browse the repository at this point in the history
  • Loading branch information
lkubb committed Oct 7, 2024
1 parent f46e4da commit 72a443d
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 16 deletions.
1 change: 1 addition & 0 deletions changelog/+minmaxsalt.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ensured minimum and maximum supported Salt versions always at least follow their default values (supported versions at the time of the template release) when updating the template.
1 change: 1 addition & 0 deletions changelog/+workflows.removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dropped `workflows` question and `basic` and `org` workflow variants. All projects use the `enhanced` workflows from now on.
3 changes: 3 additions & 0 deletions copier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ max_salt_version:
{%- endif -%}
{%- endif -%}
{%- endif -%}
{%- if (max_salt_version | float) < (salt_version | float) -%}
Maximum version needs to be at least {{ salt_version }}
{%- endif -%}
no_saltext_namespace:
type: bool
Expand Down
50 changes: 41 additions & 9 deletions tasks/migrations.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,48 @@
"""
Run migrations between template versions during updates.
"""

from packaging.version import Version
from task_helpers.copier import load_data_yaml
from task_helpers.migrate import COPIER_CONF
from task_helpers.migrate import migration
from task_helpers.migrate import run_migrations
from task_helpers.migrate import status
from task_helpers.migrate import sync_minimum_version
from task_helpers.migrate import var_migration

# 0.5.0 migrates all projects to the enhanced workflows, which
# require accurate Salt versions to generate sensible test matrices.
# The default values always represent the extremes, so sync them
# during all updates from now on. This avoids having to create
# separate migrations for future updates.
sync_minimum_version(None, "max_salt_version")
sync_salt_version = sync_minimum_version(None, "salt_version")


@migration(None, "before", desc=False, after=sync_salt_version)
def ensure_minimum_python_requires(answers):
"""
Ensure the minimum Python version is respected during each update.
We cannot use sync_minimum_version for this because the default
value is computed in Jinja. Let's replicate the Jinja here.
"""
if "python_requires" not in answers:
return

salt_python_support = load_data_yaml("salt_python_support")
selected_salt_version = float(
int(answers.get("salt_version", COPIER_CONF["salt_version"]["default"]))
)
default = ".".join(str(x) for x in salt_python_support[selected_salt_version]["min"])
current = answers["python_requires"]

if Version(str(current)) < Version(str(default)):
new = type(current)(default)
status(f"Answer migration: Updating python_requires from {current!r} to {new!r}")
answers["python_requires"] = new
return answers


@var_migration("0.4.5", "max_salt_version")
def migrate_045_max_salt_version(val):
Expand All @@ -21,14 +62,5 @@ def migrate_037_docs_url(val):
return ...


@var_migration("0.3.0", "python_requires")
def migrate_030_python_requires(val):
"""
Raise minimum Python version to 3.8.
"""
if Version(val) < Version("3.8"):
return "3.8"


if __name__ == "__main__":
run_migrations()
24 changes: 24 additions & 0 deletions tasks/task_helpers/copier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from pathlib import Path

import copier.template
import yaml

TEMPLATE_ROOT = (Path(__file__).parent.parent.parent).resolve()


def load_copier_conf():
"""
Load the complete Copier configuration.
"""
return copier.template.load_template_config(TEMPLATE_ROOT / "copier.yml")


def load_data_yaml(name):
"""
Load a file in the template root `data` directory.
"""
file = (TEMPLATE_ROOT / "data" / name).with_suffix(".yaml")
if not file.exists():
raise OSError(f"The file '{file}' does not exist")
with open(file, encoding="utf-8") as f:
return yaml.safe_load(f)
64 changes: 57 additions & 7 deletions tasks/task_helpers/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from packaging.version import Version

from .copier import load_copier_conf
from .pythonpath import project_tools

with project_tools():
Expand All @@ -17,6 +18,7 @@


MIGRATIONS = []
COPIER_CONF = load_copier_conf()


def run_migrations():
Expand Down Expand Up @@ -51,7 +53,7 @@ def _run_migrations_after(answers):
func(answers.copy())


def migration(trigger, stage="after", desc=None):
def migration(trigger, stage="after", desc=None, after=None):
"""
Decorator for declaring a general migration.
Expand Down Expand Up @@ -108,15 +110,20 @@ def wrapper(func):
# Other decorators delegate to this one, only create a general
# migration if it's not already another subtype
if not isinstance(func, Migration):
func = Migration(func, desc=desc or func.__name__.replace("_", " "))
nonlocal desc
if desc is None:
desc = func.__name__.replace("_", " ")
elif not desc:
desc = None
func = Migration(func, desc=desc)
global MIGRATIONS
MIGRATIONS.append((trigger_version, func))
return func

return wrapper


def var_migration(trigger, varname):
def var_migration(trigger, varname, after=None):
"""
Decorator for declaring an answer migration, e.g. when raising
a minimum version or changing an answer's type.
Expand All @@ -142,25 +149,68 @@ def migrate_foo_100(val):
"""

def wrapper(func):
return migration(trigger, "before")(VarMigration(func, varname))
return migration(trigger, "before")(VarMigration(func, varname, after=after))

return wrapper


def raise_minimum_version(trigger, varname, minimum):
"""
Register a migration to increase the value of an answer representing
a version to a minimum value.
Examples:
raise_minimum_version("1.0.0", "python", 3.8)
raise_minimum_version("1.0.0", "python", "3.8")
"""

def _raise_minimum(var):
if Version(str(var)) < Version(str(minimum)):
return type(var)(minimum)

return var_migration(trigger, varname)(_raise_minimum)


def sync_minimum_version(trigger, varname):
"""
Register a migration to increase the value of an answer representing
a version to a minimum value, determined by its default answer.
"""
default = COPIER_CONF[varname]["default"]
return raise_minimum_version(trigger, varname, default)


class Migration:
def __init__(self, func, desc=None):
def __init__(self, func, desc=None, after=None):
self.func = func
self.desc = desc
after = after or []
if not isinstance(after, list):
after = [after]
self.after = after

def __call__(self, answers):
if self.desc is not None:
status(f"Running migration: {self.desc}")
return self.func(answers)

def __lt__(self, other):
"""
Ensure we can sort the list of migrations, even if there
are multiple migrations for a single version.
This also allows explicit ordering of migrations of the
same version (or those without one).
"""
if not isinstance(other, Migration):
raise TypeError(f"Cannot compare Migration to {other!r}")
return any(migration is self for migration in other.after)


class VarMigration(Migration):
def __init__(self, func, varname):
super().__init__(func)
def __init__(self, func, varname, after=None):
super().__init__(func, after=after)
self.varname = varname

def __call__(self, answers):
Expand Down

0 comments on commit 72a443d

Please sign in to comment.