Generic build for other GE sites: no WJ data, one shopfloor type, Windows USB builder
Everything West Jefferson specific moved out to a separate repo (pxe-site-wj): NTLARS/UDC/heat-treat backups keyed by WJ machine numbers, CMM and Wax/Trace bay lists, WJ OpenText profiles, site-config.json, the WJ preinstall list, and the WJ shopfloor PC types (Collections, NoCollections, Common, Keyence, CMM, Genspect, HeatTreat, Wax/Trace, Display, PartMarker) with GE-Enforce, machine numbers and the desktop layout. The part-marker bench tools moved to their own repo. Dead scripts removed: build-proxmox-iso, deploy-bios, sync_hardware_models, mirror-from-gold, pull-bios, startnet-template.cmd. Shopfloor is one generic type, gea-shopfloor-standard, with today's timing: - PPKG at first logon (unattend Order 10, unchanged). - Install-AppList.ps1 runs C:\PreInstall\preinstall.json during setup. - A new SYSTEM task, GE Shopfloor Post-PPKG, runs postppkg.json once after the post-PPKG reboot. It also clears AutoLogonCount after the ShopFloor handover (the Users-level monitor can be denied that HKLM write) and removes the Intune Sync task when done. - Both lists ship empty. A site adds entries and installers. - WinPE choice 3 goes straight to the PPKG menu. Staging, fetch and heal carry only the generic payload. The webapp's shopfloor sub-menu editor is gone, and the imaging tiles name the new stages. - The playbook deploys the empty lists only if missing. It now deploys the PowerShell 7 MSI the shopfloor unattend installs, which it never did before. Blancco site settings move into the webapp (Updates > Blancco releases): business location, licensing Wi-Fi SSID and password. They are rendered into the served preferences.xml and applied to every staged release, and are seeded from site-vars (site_name, blancco_wifi_ssid, blancco_wifi_password) only while none exist. The repo XML carries no site values; the plaintext Wi-Fi password is gone from it. New site_name and site_timezone settings: report and evidence-pack site name, server clock and webapp time zone. No more West Jefferson defaults in webapp/config.py or the dashboard title. Sync-BPRT.ps1 gets the server address filled in at download instead of WJ's GE address. USB on Windows: - scripts/Build-PxeUsb.ps1 runs the same checks as build-usb.sh. It writes one FAT32 CIDATA partition (ISO files plus patched grub.cfg plus payload) with diskpart and reads every file back by SHA-256. - Tested with Windows PowerShell 5.1 in the win11 VM, and the layout installs and first-boots in the pxetest VM. - scripts/stage-usb-share.sh stages the build folder on a share. - site-content/ is optional for both builders. KB docs/kb/How_To_Deploy_PXE_Server_New_Site.html: new-site deployment, Windows only, with a site-vars.yml guide. Checks: webapp tests pass (5 suites); the unattend lint and playbook syntax checks pass; every .ps1 parses; there are no WJ hosts, IPs, names or PC types in playbook/, webapp/, scripts/ or autoinstall/.
This commit is contained in:
120
webapp/tests/test_blancco_prefs.py
Normal file
120
webapp/tests/test_blancco_prefs.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""Blancco site settings (business location, licensing Wi-Fi) rendered into
|
||||
the served preferences.xml.
|
||||
|
||||
Run from the webapp folder:
|
||||
PYTHONPATH=.:<deps> python -m unittest tests.test_blancco_prefs -v
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import xml.etree.ElementTree as ET
|
||||
from unittest import mock
|
||||
|
||||
_T = tempfile.mkdtemp(prefix="pxe-blancco-prefs-test-")
|
||||
for _k, _sub in {"WEB_ROOT": "www", "UPDATES_DIR": "upd"}.items():
|
||||
os.environ.setdefault(_k, os.path.join(_T, _sub))
|
||||
os.makedirs(os.environ[_k], exist_ok=True)
|
||||
|
||||
WEBAPP = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
REPO_PREFS = os.path.join(WEBAPP, "..", "playbook", "blancco-preferences.xml")
|
||||
sys.path.insert(0, WEBAPP)
|
||||
|
||||
from services import blancco_prefs as BP # noqa: E402
|
||||
|
||||
|
||||
def served():
|
||||
root = ET.parse(BP.served_prefs_path()).getroot()
|
||||
return (root.find(".//customer_data/business_location").text,
|
||||
root.find(".//wireless/ssid").text,
|
||||
root.find(".//wireless/password").text)
|
||||
|
||||
|
||||
class BlanccoPrefs(unittest.TestCase):
|
||||
def setUp(self):
|
||||
shutil.rmtree(os.environ["UPDATES_DIR"], ignore_errors=True)
|
||||
os.makedirs(os.environ["UPDATES_DIR"])
|
||||
os.makedirs(os.path.dirname(BP.repo_prefs_path()), exist_ok=True)
|
||||
shutil.copy(REPO_PREFS, BP.repo_prefs_path())
|
||||
for p in (BP.served_prefs_path(),):
|
||||
if os.path.exists(p):
|
||||
os.remove(p)
|
||||
|
||||
def test_repo_file_carries_no_site_values(self):
|
||||
root = ET.parse(REPO_PREFS).getroot()
|
||||
self.assertFalse(root.find(".//customer_data/business_location").text)
|
||||
self.assertFalse(root.find(".//wireless/password").text)
|
||||
self.assertFalse(root.find(".//wireless/ssid").text)
|
||||
|
||||
def test_render_with_special_characters(self):
|
||||
BP.save("Smith & Sons <Plant 2>", "Example-WiFi", r'Fake-Pa55!@# \1 \g<0> "q" &')
|
||||
self.assertTrue(BP.render())
|
||||
self.assertEqual(served(), ("Smith & Sons <Plant 2>", "Example-WiFi", r'Fake-Pa55!@# \1 \g<0> "q" &'))
|
||||
|
||||
def test_rest_of_file_unchanged(self):
|
||||
BP.save("Site A", "NET", "pw")
|
||||
BP.render()
|
||||
def strip(t):
|
||||
import re
|
||||
t = re.sub(r"<business_location>[^<]*</business_location>", "", t)
|
||||
return re.sub(r'(<ssid encrypted="false">)[^<]*(</ssid>\s*<password encrypted="false">)[^<]*', r"\1\2", t)
|
||||
with open(REPO_PREFS) as a, open(BP.served_prefs_path()) as b:
|
||||
self.assertEqual(strip(a.read()), strip(b.read()))
|
||||
|
||||
def test_render_is_idempotent(self):
|
||||
BP.save("Site A", "NET", "pw")
|
||||
self.assertTrue(BP.render())
|
||||
self.assertFalse(BP.render())
|
||||
|
||||
def test_blank_password_keeps_the_stored_one(self):
|
||||
BP.save("Site A", "NET", "first")
|
||||
BP.save("Site B", "NET2", "")
|
||||
BP.save("Site C", "NET3", None)
|
||||
s = BP.load()
|
||||
self.assertEqual((s["business_location"], s["wifi_ssid"], s["wifi_password"]), ("Site C", "NET3", "first"))
|
||||
|
||||
def test_public_never_returns_the_password(self):
|
||||
BP.save("Site A", "NET", "secret-pw")
|
||||
pub = BP.public()
|
||||
self.assertNotIn("secret-pw", json.dumps(pub))
|
||||
self.assertTrue(pub["wifi_password_set"])
|
||||
|
||||
def test_settings_file_is_private(self):
|
||||
BP.save("Site A", "NET", "pw")
|
||||
self.assertEqual(stat.S_IMODE(os.stat(BP.settings_path()).st_mode), 0o600)
|
||||
|
||||
def test_empty_ssid_refused(self):
|
||||
with self.assertRaisesRegex(ValueError, "SSID"):
|
||||
BP.save("Site A", " ", "pw")
|
||||
|
||||
def test_control_characters_refused(self):
|
||||
with self.assertRaisesRegex(ValueError, "control"):
|
||||
BP.save("Site\nA", "NET", "pw")
|
||||
|
||||
def test_unset_location_renders_unnamed_site(self):
|
||||
BP.save("", "NET", "")
|
||||
BP.render()
|
||||
self.assertEqual(served()[0], "Unnamed site")
|
||||
|
||||
def test_seed_only_when_no_settings_exist(self):
|
||||
env = {"BLANCCO_SEED_LOCATION": "Seeded Site", "BLANCCO_SEED_SSID": "SEEDNET",
|
||||
"BLANCCO_SEED_PASSWORD": "seedpw"}
|
||||
with mock.patch.dict(os.environ, env):
|
||||
self.assertEqual(BP.main(["render", "--seed"]), 0)
|
||||
self.assertEqual(served(), ("Seeded Site", "SEEDNET", "seedpw"))
|
||||
# Changed in the webapp afterwards: a later playbook run keeps it.
|
||||
BP.save("Webapp Site", "WEBNET", "webpw")
|
||||
with mock.patch.dict(os.environ, env):
|
||||
BP.main(["render", "--seed"])
|
||||
self.assertEqual(served(), ("Webapp Site", "WEBNET", "webpw"))
|
||||
|
||||
def test_apply_refuses_unknown_layout(self):
|
||||
with self.assertRaises(ValueError):
|
||||
BP.apply(b"<preferences><x/></preferences>")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user