Skip to content

Commit 26038b7

Browse files
committed
.github/workflows/build.yml: Flash an available platform
Upstream-Status: Inappropriate [Dasharo downstream] Change-Id: I7439052efbce39be7fd12bde6b7f024146503d66 Signed-off-by: Michał Kopeć <michal.kopec@3mdeb.com>
1 parent eda7ebc commit 26038b7

2 files changed

Lines changed: 174 additions & 0 deletions

File tree

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env python3
2+
"""Flash a coreboot binary to a free SnipeIT-managed DUT via osfv_cli.
3+
4+
Looks up a Ready-to-Deploy, unassigned asset for the given model in SnipeIT,
5+
reads its RTE IP from a custom field, checks the asset out to the calling
6+
user, runs `osfv_cli rte --rte_ip <ip> flash write --rom <rom>`, then checks
7+
the asset back in (always, even if the flash fails).
8+
9+
Env vars:
10+
SNIPEIT_IP SnipeIT host IP/hostname (default: 192.168.4.202)
11+
SNIPEIT_API_KEY Personal API token (required)
12+
SNIPEIT_UID Numeric user ID to check out to. Optional; if unset,
13+
falls back to GET /api/v1/users/me (newer SnipeIT only).
14+
"""
15+
16+
import argparse
17+
import os
18+
import subprocess
19+
import sys
20+
21+
import requests
22+
23+
24+
SNIPEIT_IP = os.environ.get("SNIPEIT_IP", "192.168.4.202").strip().rstrip("/")
25+
SNIPEIT_URL = f"http://{SNIPEIT_IP}"
26+
SNIPEIT_API_KEY = os.environ.get("SNIPEIT_API_KEY")
27+
READY_TO_DEPLOY = "Ready to Deploy"
28+
29+
30+
def api(method, path, **kwargs):
31+
if not SNIPEIT_API_KEY:
32+
sys.exit("SNIPEIT_API_KEY env var is required")
33+
headers = kwargs.pop("headers", {})
34+
headers.update({
35+
"Authorization": f"Bearer {SNIPEIT_API_KEY}",
36+
"Accept": "application/json",
37+
"Content-Type": "application/json",
38+
})
39+
r = requests.request(
40+
method, f"{SNIPEIT_URL}/api/v1{path}",
41+
headers=headers, timeout=30, **kwargs,
42+
)
43+
r.raise_for_status()
44+
data = r.json()
45+
# SnipeIT returns HTTP 200 with {"status":"error",...} on logical errors.
46+
if isinstance(data, dict) and data.get("status") == "error":
47+
sys.exit(f"SnipeIT error on {method} {path}: {data.get('messages')}")
48+
return data
49+
50+
51+
def find_model_id(name):
52+
data = api("GET", "/models", params={"search": name, "limit": 50})
53+
rows = data.get("rows", [])
54+
exact = [m for m in rows if m["name"].lower() == name.lower()]
55+
matches = exact or rows
56+
if not matches:
57+
sys.exit(f"No SnipeIT model matches '{name}'")
58+
if len(matches) > 1:
59+
names = ", ".join(sorted(m["name"] for m in matches))
60+
sys.exit(f"Ambiguous model name '{name}': {names}")
61+
return matches[0]["id"]
62+
63+
64+
def find_free_asset(model_id):
65+
data = api("GET", "/hardware", params={
66+
"model_id": model_id,
67+
"status": "RTD",
68+
"limit": 100,
69+
})
70+
for asset in data.get("rows", []):
71+
status = (asset.get("status_label") or {}).get("name")
72+
if status != READY_TO_DEPLOY:
73+
continue
74+
if asset.get("assigned_to"):
75+
continue
76+
return asset
77+
sys.exit(
78+
f"No '{READY_TO_DEPLOY}' unassigned asset found for model_id={model_id}"
79+
)
80+
81+
82+
def get_rte_ip(asset):
83+
fields = asset.get("custom_fields") or {}
84+
for key, payload in fields.items():
85+
k = key.lower()
86+
if "rte" in k and "ip" in k:
87+
value = (payload or {}).get("value")
88+
if value:
89+
return value
90+
sys.exit(f"Asset {asset.get('asset_tag')} has no RTE IP custom field")
91+
92+
93+
def get_my_user_id():
94+
env = os.environ.get("SNIPEIT_UID")
95+
if env:
96+
return int(env)
97+
return api("GET", "/users/me")["id"]
98+
99+
100+
def checkout(asset_id, user_id):
101+
api("POST", f"/hardware/{asset_id}/checkout", json={
102+
"checkout_to_type": "user",
103+
"assigned_user": user_id,
104+
"note": "flash_via_snipeit.py auto-checkout",
105+
})
106+
107+
108+
def checkin(asset_id):
109+
api("POST", f"/hardware/{asset_id}/checkin", json={
110+
"note": "flash_via_snipeit.py auto-checkin",
111+
})
112+
113+
114+
def flash(rte_ip, rom_path):
115+
cmd = ["osfv_cli", "rte", "--rte_ip", rte_ip,
116+
"flash", "write", "--rom", rom_path]
117+
print("+ " + " ".join(cmd), flush=True)
118+
return subprocess.run(cmd, check=False).returncode
119+
120+
121+
def main():
122+
ap = argparse.ArgumentParser(
123+
description="Flash a coreboot binary to a SnipeIT-managed DUT."
124+
)
125+
ap.add_argument("rom", help="Path to coreboot binary to flash")
126+
ap.add_argument("model", help="Platform/model name as it appears in SnipeIT")
127+
args = ap.parse_args()
128+
129+
if not os.path.isfile(args.rom):
130+
sys.exit(f"ROM file not found: {args.rom}")
131+
132+
user_id = get_my_user_id()
133+
model_id = find_model_id(args.model)
134+
asset = find_free_asset(model_id)
135+
rte_ip = get_rte_ip(asset)
136+
asset_id = asset["id"]
137+
asset_tag = asset["asset_tag"]
138+
print(f"Selected asset {asset_tag} (id={asset_id}), RTE IP {rte_ip}")
139+
140+
print(f"Checking out {asset_tag} to user_id={user_id}...")
141+
checkout(asset_id, user_id)
142+
try:
143+
rc = flash(rte_ip, args.rom)
144+
finally:
145+
print(f"Checking in {asset_tag}...")
146+
try:
147+
checkin(asset_id)
148+
except Exception as e:
149+
print(f"WARNING: check-in failed: {e}", file=sys.stderr)
150+
sys.exit(rc)
151+
152+
153+
if __name__ == "__main__":
154+
main()

.github/workflows/build.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,26 @@ jobs:
215215
build/coreboot.rom
216216
build/coreboot.cap
217217
retention-days: 30
218+
flash_protectli_vp66xx:
219+
if: github.event_name == 'push' && github.ref == 'refs/heads/dasharo-25.12'
220+
needs: build_protectli
221+
runs-on: self-hosted
222+
steps:
223+
- name: Checkout repository
224+
uses: actions/checkout@v5
225+
with:
226+
path: src
227+
- name: Download firmware artifact
228+
uses: actions/download-artifact@v5
229+
with:
230+
name: dasharo-protectli-vp66xx
231+
path: artifact
232+
- name: Flash via SnipeIT-managed RTE
233+
env:
234+
SNIPEIT_IP: ${{ secrets.SNIPEIT_IP }}
235+
SNIPEIT_API_KEY: ${{ secrets.SNIPEIT_API_KEY }}
236+
SNIPEIT_UID: ${{ secrets.SNIPEIT_UID }}
237+
run: python3 src/.github/scripts/flash_via_snipeit.py artifact/coreboot.rom "VP66xx"
218238
build_pcengines:
219239
runs-on: ubuntu-24.04
220240
strategy:

0 commit comments

Comments
 (0)