Kiosks report their display type; whole-chain DDL parity
This commit is contained in:
@@ -210,7 +210,8 @@ function New-ShopdbReport {
|
||||
param([string]$Hostname = $env:COMPUTERNAME,
|
||||
[Parameter(Mandatory)][string]$Scope,
|
||||
[int]$AppliedVersion,
|
||||
[Parameter(Mandatory)][hashtable]$Summary)
|
||||
[Parameter(Mandatory)][hashtable]$Summary,
|
||||
[string]$SubType = (Get-ShopdbSubType))
|
||||
$results = @(foreach ($entry in @($Summary.Results)) {
|
||||
if ($null -eq $entry) { continue }
|
||||
$mapped = @{
|
||||
@@ -230,6 +231,7 @@ function New-ShopdbReport {
|
||||
scopename = $Scope
|
||||
appliedversion = $AppliedVersion
|
||||
enforcerversion = $Summary.EnforcerVersion
|
||||
subtype = $SubType
|
||||
counts = @{
|
||||
installed = [int]$Summary.Installed
|
||||
skipped = [int]$Summary.Skipped
|
||||
@@ -240,6 +242,32 @@ function New-ShopdbReport {
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ShopdbSubType {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
What this PC is WITHIN its scope, read off the machine itself.
|
||||
|
||||
.DESCRIPTION
|
||||
A display knows whether it is a Dashboard, a Lobby screen or the 3D print
|
||||
room: the dispatcher reads C:\Enrollment\display-type.txt to choose which
|
||||
page to open. It just never told shopdb, so the fleet table had to guess
|
||||
from a DashboardDefault fqdn mapping that is empty unless somebody added a
|
||||
row per kiosk. Reported by the device beats inferred from a lookup table,
|
||||
the same way enforcerversion already works.
|
||||
|
||||
Returns '' when the file is absent - every non-display PC type - so the
|
||||
report simply carries no subtype rather than a made-up one.
|
||||
#>
|
||||
param([string]$Path = 'C:\Enrollment\display-type.txt')
|
||||
try {
|
||||
if (-not (Test-Path $Path)) { return '' }
|
||||
$value = (Get-Content -Path $Path -TotalCount 1 -ErrorAction Stop)
|
||||
return ([string]$value).Trim()
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ShopdbPayload {
|
||||
<#
|
||||
Fetch a payload blob by content hash over HTTPS, verify the sha256, and
|
||||
@@ -449,4 +477,4 @@ function ConvertTo-ShopdbSummary {
|
||||
Export-ModuleMember -Function Get-ShopdbConfig, Sync-ShopdbManifest, `
|
||||
Compare-ShopdbShadow, Send-ShopdbReport, New-ShopdbReport, Read-CachedVersion, `
|
||||
Get-ShopdbPayload, Resolve-ShopdbPayloads, Merge-ShopdbManifests, `
|
||||
ConvertTo-ShopdbSummary
|
||||
ConvertTo-ShopdbSummary, Get-ShopdbSubType
|
||||
|
||||
@@ -22,9 +22,9 @@ from alembic.migration import MigrationContext
|
||||
from alembic.operations import Operations
|
||||
|
||||
|
||||
_BASELINE = (Path(__file__).resolve().parent.parent.parent / 'plugins' /
|
||||
'geenforce' / 'migrations' / 'versions' /
|
||||
'0001_geenforce_baseline.py')
|
||||
_VERSIONS = (Path(__file__).resolve().parent.parent.parent / 'plugins' /
|
||||
'geenforce' / 'migrations' / 'versions')
|
||||
_BASELINE = _VERSIONS / '0001_geenforce_baseline.py'
|
||||
|
||||
|
||||
def _geenforce_models():
|
||||
@@ -53,15 +53,23 @@ def _model_schema(db, models):
|
||||
|
||||
|
||||
def _migration_schema():
|
||||
"""Run the baseline migration's upgrade() on a fresh engine; return columns.
|
||||
"""Run the WHOLE chain's upgrade() on a fresh engine; return columns.
|
||||
|
||||
The whole chain, not just the baseline. Reading only 0001 meant a column
|
||||
added by a later revision read as model/migration drift even though the
|
||||
migration for it existed - the check is "do the models match what the
|
||||
migrations build", and that is the chain, not its first file.
|
||||
|
||||
Returns {tablename: {columnname: reflected-column-dict}}. Reflection happens
|
||||
while the connection is open, so we materialize the dicts before it closes.
|
||||
"""
|
||||
spec = importlib.util.spec_from_file_location('geenforce_baseline',
|
||||
str(_BASELINE))
|
||||
migration = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(migration)
|
||||
migrations = []
|
||||
for path in sorted(_VERSIONS.glob('[0-9]*.py')):
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
'geenforce_migration_' + path.stem, str(path))
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
migrations.append(module)
|
||||
|
||||
engine = create_engine('sqlite://')
|
||||
schema = {}
|
||||
@@ -69,7 +77,8 @@ def _migration_schema():
|
||||
operations = Operations(MigrationContext.configure(connection))
|
||||
operations._install_proxy()
|
||||
try:
|
||||
migration.upgrade()
|
||||
for migration in migrations:
|
||||
migration.upgrade()
|
||||
finally:
|
||||
operations._remove_proxy()
|
||||
connection.commit()
|
||||
|
||||
@@ -193,3 +193,37 @@ def test_backup_state_prefers_the_newest_confirmed_revision(db):
|
||||
|
||||
assert facts['wjpc01']['backupkind'] == 'udc'
|
||||
assert facts['wjpc01']['backuplastseen'] is not None
|
||||
|
||||
|
||||
# -- reported display subtype -------------------------------------------------
|
||||
|
||||
def test_report_stores_the_subtype_the_device_reports(db):
|
||||
"""A kiosk knows whether it is a Dashboard or a Lobby; it now says so.
|
||||
|
||||
Previously the fleet table inferred this from the DashboardDefault fqdn
|
||||
mapping, which is empty unless somebody added a row per kiosk, so the
|
||||
column was blank for every display.
|
||||
"""
|
||||
from plugins.geenforce.service import record_enforcement_report
|
||||
|
||||
record_enforcement_report({'hostname': 'WJKIOSK01', 'scopename': 'gea-shopfloor-display',
|
||||
'subtype': 'Lobby', 'counts': {}})
|
||||
db.session.flush()
|
||||
|
||||
stored = ManifestEnforcementReport.query.filter_by(
|
||||
hostname='WJKIOSK01', iscurrent=True).one()
|
||||
assert stored.subtype == 'Lobby'
|
||||
|
||||
|
||||
def test_report_without_a_subtype_stores_none(db):
|
||||
"""Every non-display PC type has no display-type.txt, so it reports no
|
||||
subtype - that must land as NULL, not as an empty string."""
|
||||
from plugins.geenforce.service import record_enforcement_report
|
||||
|
||||
record_enforcement_report({'hostname': 'WJPC77', 'scopename': 'common',
|
||||
'subtype': '', 'counts': {}})
|
||||
db.session.flush()
|
||||
|
||||
stored = ManifestEnforcementReport.query.filter_by(
|
||||
hostname='WJPC77', iscurrent=True).one()
|
||||
assert stored.subtype is None
|
||||
|
||||
Reference in New Issue
Block a user