add Entra device identity to computers and Azure/Thor import sources

PC records now carry Entra/Azure device identity so imported machines keep a durable link back to their directory object instead of matching on hostname alone. Adds azuredeviceid, azureobjectid, and azuredevicecategory to the Computer model with matching migrations, surfaces them read-only in PCDetail as an Entra Device Info card, and lets PCForm edit the device id directly. The computers plugin registers a machineassetnumber import hook that reconciles PC-to-machine links during bulk import and warns on unresolved or conflicting relationships.

Adds importsources adapters that normalize Azure device-group and Thor PC inventory exports into canonical ShopDB CSVs via hostname alignment, exposes register_import_hook and RowWarning on the shopdb.api contract surface (contract 0.21.0), and refreshes the Import Site Assets page with clearer source instructions. Includes reconcile_locations maintenance script and coverage for the new import paths.
This commit is contained in:
benjamin.baker
2026-08-25 16:46:00 -04:00
parent f4d8868463
commit bdef21da50
21 changed files with 2064 additions and 86 deletions

View File

@@ -0,0 +1,40 @@
"""Add azuredeviceid to computers (Entra/Azure device-group imports).
The core chain created computers WITHOUT an azuredeviceid column. This plugin
revision adds it so BOTH fresh installs (core chain builds the table, then this
adds the column) and existing installs get it. Guarded/idempotent: skips when
the table is absent (plugin disabled) or the column already exists (e.g. a test
DB built by db.create_all() from the model).
Revision ID: computers0002azureid
Revises: computers0001anchor
"""
from alembic import op
import sqlalchemy as sa
revision = 'computers0002azureid'
down_revision = 'computers0001anchor'
branch_labels = None
depends_on = None
def upgrade():
bind = op.get_bind()
insp = sa.inspect(bind)
if 'computers' not in insp.get_table_names():
return
cols = {c['name'] for c in insp.get_columns('computers')}
if 'azuredeviceid' not in cols:
op.add_column('computers',
sa.Column('azuredeviceid', sa.String(length=64), nullable=True))
def downgrade():
bind = op.get_bind()
insp = sa.inspect(bind)
if 'computers' not in insp.get_table_names():
return
cols = {c['name'] for c in insp.get_columns('computers')}
if 'azuredeviceid' in cols:
op.drop_column('computers', 'azuredeviceid')

View File

@@ -0,0 +1,45 @@
"""Add azureobjectid and azuredevicecategory to computers.
The Azure device-group import now also records the Entra directory object id
(the CSV 'id' column) and an operator-supplied device category (the group
membership label, e.g. SFLD_DYN_Device_Group_...). This revision adds both
columns. Guarded/idempotent: skips when the table is absent (plugin disabled)
or a column already exists (e.g. a test DB built by db.create_all()).
Revision ID: computers0003azurefields
Revises: computers0002azureid
"""
from alembic import op
import sqlalchemy as sa
revision = 'computers0003azurefields'
down_revision = 'computers0002azureid'
branch_labels = None
depends_on = None
def upgrade():
bind = op.get_bind()
insp = sa.inspect(bind)
if 'computers' not in insp.get_table_names():
return
cols = {c['name'] for c in insp.get_columns('computers')}
if 'azureobjectid' not in cols:
op.add_column('computers',
sa.Column('azureobjectid', sa.String(length=64), nullable=True))
if 'azuredevicecategory' not in cols:
op.add_column('computers',
sa.Column('azuredevicecategory', sa.String(length=255), nullable=True))
def downgrade():
bind = op.get_bind()
insp = sa.inspect(bind)
if 'computers' not in insp.get_table_names():
return
cols = {c['name'] for c in insp.get_columns('computers')}
if 'azuredevicecategory' in cols:
op.drop_column('computers', 'azuredevicecategory')
if 'azureobjectid' in cols:
op.drop_column('computers', 'azureobjectid')