diff --git a/steward/migrations/versions/0017_ansible_run_scope.py b/steward/migrations/versions/0017_ansible_run_scope.py new file mode 100644 index 0000000..765f71b --- /dev/null +++ b/steward/migrations/versions/0017_ansible_run_scope.py @@ -0,0 +1,41 @@ +"""Add inventory_scope to ansible_runs; make inventory_path nullable; backfill scope + +Revision ID: 0017_ansible_run_scope +Revises: 0016_ansible_inventory_groups +Create Date: 2026-06-05 +""" +from typing import Sequence, Union +from alembic import op +import sqlalchemy as sa + +revision: str = "0017_ansible_run_scope" +down_revision: Union[str, None] = "0016_ansible_inventory_groups" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "ansible_runs", + sa.Column("inventory_scope", sa.String(255), nullable=True), + ) + # Backfill: existing rows used repo inventory files — reconstruct the legacy scope string. + op.execute( + """ + UPDATE ansible_runs + SET inventory_scope = 'repo:' || source_name || ':' || inventory_path + WHERE inventory_path IS NOT NULL AND inventory_path != '' + """ + ) + # Rows with empty inventory_path get 'steward:all' + op.execute( + "UPDATE ansible_runs SET inventory_scope = 'steward:all' WHERE inventory_scope IS NULL" + ) + op.alter_column("ansible_runs", "inventory_scope", nullable=False) + # Make inventory_path nullable — new steward:* runs won't have a file path. + op.alter_column("ansible_runs", "inventory_path", nullable=True) + + +def downgrade() -> None: + op.drop_column("ansible_runs", "inventory_scope") + op.alter_column("ansible_runs", "inventory_path", nullable=False) diff --git a/steward/models/ansible.py b/steward/models/ansible.py index 2adf06e..36731dc 100644 --- a/steward/models/ansible.py +++ b/steward/models/ansible.py @@ -19,7 +19,10 @@ class AnsibleRun(Base): id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) playbook_path: Mapped[str] = mapped_column(String(512), nullable=False) - inventory_path: Mapped[str] = mapped_column(String(512), nullable=False) + inventory_path: Mapped[str | None] = mapped_column(String(512), nullable=True) + inventory_scope: Mapped[str] = mapped_column( + String(255), nullable=False, default="steward:all" + ) source_name: Mapped[str] = mapped_column(String(128), nullable=False) # Nullable: automated runs (alert actions, schedules) have no human actor — # NULL renders as "system". Manual runs still record the triggering user.