Flask + Vue rewrite of the classic-ASP shopdb, built as a plugin framework so sister GE Aerospace sites can adopt it. Product version 0.7.0-pre-0.8.0, plugin contract 0.11.0, 966 backend tests. Key entry points: - docs/INSTALL-WINDOWS-IIS.md - tested Windows + IIS install runbook (own-site and subpath deployment methods) - docs/PILOT-DEPLOY.md - production pilot: stand up + import + verify - docs/IMPORT-API.md - schema-agnostic legacy data import contract - scripts/site_imports/wjf/ - West Jefferson reference import loader - docs/adr/ - twelve accepted architecture decision records
167 lines
7.1 KiB
Python
167 lines
7.1 KiB
Python
"""GE-Enforce compliance: fleet-install coverage per app-linked entry.
|
|
|
|
Compliance pairs each app-linked manifest entry with collected PC data
|
|
(ComputerInstalledApp) to answer 'how many PCs have this app, and how many on
|
|
the expected version'. Only entries carrying a curated appid appear; unlinked
|
|
entries are skipped. FileVersion detection supplies the expected-version target;
|
|
every other detection method yields a null target (no meaningful match). When
|
|
the optional computers plugin is absent, counts degrade to null gracefully.
|
|
"""
|
|
|
|
from shopdb.core.models import Asset, AssetType, Application
|
|
|
|
from plugins.geenforce import service
|
|
|
|
|
|
def _seed_app(db, appname):
|
|
app_row = Application(appname=appname)
|
|
db.session.add(app_row)
|
|
db.session.commit()
|
|
return app_row.appid
|
|
|
|
|
|
def _seed_pc_with_app(db, hostname, appid, installedversion, isactive=True):
|
|
"""Create a Computer + one ComputerInstalledApp row for it."""
|
|
from plugins.computers.models import Computer, ComputerInstalledApp
|
|
atype = AssetType.query.filter_by(assettype='computer').first()
|
|
if not atype:
|
|
atype = AssetType(assettype='computer')
|
|
db.session.add(atype)
|
|
db.session.flush()
|
|
asset = Asset(assetnumber=f'PC-{hostname}', assettypeid=atype.assettypeid)
|
|
db.session.add(asset)
|
|
db.session.flush()
|
|
comp = Computer(assetid=asset.assetid, hostname=hostname)
|
|
db.session.add(comp)
|
|
db.session.flush()
|
|
link = ComputerInstalledApp(computerid=comp.computerid, appid=appid,
|
|
installedversion=installedversion,
|
|
isactive=isactive)
|
|
db.session.add(link)
|
|
db.session.commit()
|
|
return comp
|
|
|
|
|
|
def _create_scope(client, auth_headers, name='gea-shopfloor-cmm'):
|
|
resp = client.post('/api/geenforce/scopes',
|
|
json={'scopename': name, 'phase': 'runtime'},
|
|
headers=auth_headers)
|
|
assert resp.status_code == 201, resp.get_json()
|
|
return resp.get_json()['data']['scopeid']
|
|
|
|
|
|
def _add_entry(client, auth_headers, scopeid, entry):
|
|
resp = client.post(f'/api/geenforce/scopes/{scopeid}/entries',
|
|
json=entry, headers=auth_headers)
|
|
assert resp.status_code == 201, resp.get_json()
|
|
return resp.get_json()['data']['entryid']
|
|
|
|
|
|
def test_compliance_only_lists_app_linked_entries(client, db, auth_headers):
|
|
appid = _seed_app(db, 'PC-DMIS')
|
|
scopeid = _create_scope(client, auth_headers)
|
|
_add_entry(client, auth_headers, scopeid,
|
|
{'Name': 'PC-DMIS 2019', 'Type': 'MSI', 'appid': appid,
|
|
'DetectionMethod': 'FileVersion', 'DetectionPath': 'C:\\p.exe',
|
|
'DetectionValue': '2019 R1'})
|
|
# An unlinked entry (no appid) must NOT appear.
|
|
_add_entry(client, auth_headers, scopeid,
|
|
{'Name': 'Unlinked', 'Type': 'MSI', 'Installer': 'apps/x.msi'})
|
|
|
|
data = client.get(f'/api/geenforce/scopes/{scopeid}/compliance',
|
|
headers=auth_headers).get_json()['data']
|
|
assert data['computersplugin'] is True
|
|
assert [r['entryname'] for r in data['rows']] == ['PC-DMIS 2019']
|
|
row = data['rows'][0]
|
|
assert row['appid'] == appid
|
|
assert row['appname'] == 'PC-DMIS'
|
|
assert row['expectedversion'] == '2019 R1'
|
|
|
|
|
|
def test_compliance_counts_active_installs_and_version_match(client, db,
|
|
auth_headers):
|
|
appid = _seed_app(db, 'PC-DMIS')
|
|
# Two PCs on the expected version, one on a different version, one inactive.
|
|
_seed_pc_with_app(db, 'CMM01', appid, '2019 R1')
|
|
_seed_pc_with_app(db, 'CMM02', appid, '2019 R1')
|
|
_seed_pc_with_app(db, 'CMM03', appid, '2016 R2')
|
|
_seed_pc_with_app(db, 'CMM04', appid, '2019 R1', isactive=False)
|
|
|
|
scopeid = _create_scope(client, auth_headers)
|
|
_add_entry(client, auth_headers, scopeid,
|
|
{'Name': 'PC-DMIS 2019', 'Type': 'MSI', 'appid': appid,
|
|
'DetectionMethod': 'FileVersion', 'DetectionPath': 'C:\\p.exe',
|
|
'DetectionValue': '2019 R1'})
|
|
|
|
row = client.get(f'/api/geenforce/scopes/{scopeid}/compliance',
|
|
headers=auth_headers).get_json()['data']['rows'][0]
|
|
# Inactive row excluded: 3 active installs, 2 on the expected version.
|
|
assert row['installedcount'] == 3
|
|
assert row['versionmatchcount'] == 2
|
|
assert '2 of 3' in row['coveragenote']
|
|
|
|
|
|
def test_compliance_non_fileversion_has_null_version_target(client, db,
|
|
auth_headers):
|
|
appid = _seed_app(db, 'eDNC')
|
|
_seed_pc_with_app(db, 'DNC01', appid, '5.0')
|
|
scopeid = _create_scope(client, auth_headers)
|
|
# Registry detection is not a version string -> no expected-version target.
|
|
_add_entry(client, auth_headers, scopeid,
|
|
{'Name': 'eDNC', 'Type': 'MSI', 'appid': appid,
|
|
'DetectionMethod': 'Registry',
|
|
'DetectionPath': 'HKLM\\Software\\eDNC'})
|
|
|
|
row = client.get(f'/api/geenforce/scopes/{scopeid}/compliance',
|
|
headers=auth_headers).get_json()['data']['rows'][0]
|
|
assert row['expectedversion'] is None
|
|
assert row['installedcount'] == 1
|
|
assert row['versionmatchcount'] is None
|
|
assert 'no version target' in row['coveragenote']
|
|
|
|
|
|
def test_compliance_not_installed_anywhere(client, db, auth_headers):
|
|
appid = _seed_app(db, 'Orphan')
|
|
scopeid = _create_scope(client, auth_headers)
|
|
_add_entry(client, auth_headers, scopeid,
|
|
{'Name': 'Orphan', 'Type': 'MSI', 'appid': appid,
|
|
'DetectionMethod': 'FileVersion', 'DetectionPath': 'C:\\o.exe',
|
|
'DetectionValue': '1.0'})
|
|
|
|
row = client.get(f'/api/geenforce/scopes/{scopeid}/compliance',
|
|
headers=auth_headers).get_json()['data']['rows'][0]
|
|
assert row['installedcount'] == 0
|
|
assert row['versionmatchcount'] == 0
|
|
assert row['coveragenote'] == 'not installed on any collected PC'
|
|
|
|
|
|
def test_compliance_scope_not_found(client, db, auth_headers):
|
|
resp = client.get('/api/geenforce/scopes/999999/compliance',
|
|
headers=auth_headers)
|
|
assert resp.status_code == 404
|
|
|
|
|
|
def test_compliance_requires_manage_permission(client, db, member_headers):
|
|
resp = client.get('/api/geenforce/scopes/1/compliance',
|
|
headers=member_headers)
|
|
assert resp.status_code == 403
|
|
|
|
|
|
def test_compliance_graceful_without_computers_plugin(client, db, auth_headers,
|
|
monkeypatch):
|
|
appid = _seed_app(db, 'PC-DMIS')
|
|
scopeid = _create_scope(client, auth_headers)
|
|
_add_entry(client, auth_headers, scopeid,
|
|
{'Name': 'PC-DMIS 2019', 'Type': 'MSI', 'appid': appid,
|
|
'DetectionMethod': 'FileVersion', 'DetectionPath': 'C:\\p.exe',
|
|
'DetectionValue': '2019 R1'})
|
|
monkeypatch.setattr(service, '_installed_app_model', lambda: None)
|
|
|
|
data = client.get(f'/api/geenforce/scopes/{scopeid}/compliance',
|
|
headers=auth_headers).get_json()['data']
|
|
assert data['computersplugin'] is False
|
|
row = data['rows'][0]
|
|
assert row['installedcount'] is None
|
|
assert row['versionmatchcount'] is None
|
|
assert 'computers plugin not installed' in row['coveragenote']
|