facett-ops/tests/streamlit_image_matches_requirements.rs
//! **The image and the requirements file must agree about what this app runs on.**
//!
//! `py/requirements.txt` is the one writer for the demo's runtime versions — it says
//! so, and it records that they were verified on this box. The Streamlit image pins
//! the same three as build ARGs, because installing the framework is a BUILD-time
//! step and the requirements file is a RUN-time bind mount, so the recipe cannot
//! read it.
//!
//! Two copies with no writer between them is the drift this repo has now paid for
//! twice in one day (the python version lived in four files; one of them was six
//! releases behind). FOUND on the first build of this image: it pinned streamlit
//! **1.42.2** while `requirements.txt` said **1.60.0** — a number I picked, silently
//! disagreeing with the one that had been verified. This test is why that lasted
//! minutes instead of weeks.
use std::path::{Path, PathBuf};
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().to_path_buf()
}
/// `name==version` out of a pip requirements file.
fn required(name: &str) -> String {
let p = repo_root().join("py/requirements.txt");
let text = std::fs::read_to_string(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()));
for line in text.lines() {
let t = line.trim();
if t.starts_with('#') || t.is_empty() {
continue;
}
if let Some((n, v)) = t.split_once("==") && n.trim().eq_ignore_ascii_case(name) {
return v.trim().to_string();
}
}
panic!("`{name}` is not pinned in py/requirements.txt");
}
/// What the Containerfile pins, read from the recipe the xtask actually ships.
fn image_pin(name: &str) -> String {
let cf = facett_ops::holger::STREAMLIT_CONTAINERFILE;
// Either an ARG (`ARG STREAMLIT_VERSION=1.60.0`) or an inline pip pin
// (`"grpcio==1.83.0"`). Both forms are checked so the test does not depend on
// which one the recipe happens to use for a given package.
// The ARG form is checked FIRST. The pip line may reference the ARG
// (`streamlit==${STREAMLIT_VERSION}`), and reading that would compare a shell
// variable to a version number and call it a mismatch — a test failing on its
// own parser rather than on the thing it guards.
let arg = format!("ARG {}_VERSION=", name.to_uppercase());
if let Some(at) = cf.find(&arg) {
let rest = &cf[at + arg.len()..];
let end = rest.find('\n').unwrap_or(rest.len());
return rest[..end].trim().to_string();
}
let needle = format!("{name}==");
if let Some(at) = cf.find(&needle) {
let rest = &cf[at + needle.len()..];
let end = rest.find(['"', ' ', '\\', '\n']).unwrap_or(rest.len());
return rest[..end].trim().to_string();
}
panic!("the streamlit Containerfile pins no version for `{name}`");
}
#[test]
fn streamlit_is_pinned_to_the_verified_version() {
assert_eq!(
image_pin("streamlit"),
required("streamlit"),
"the image and py/requirements.txt disagree about the streamlit version — the \
demo would be developed against one and run against another"
);
}
#[test]
fn the_grpc_stack_is_pinned_to_the_verified_versions() {
// These two are for the LIVE panes. A mismatch here surfaces as a protobuf
// runtime error deep inside a pane, which reads as a korp-server problem.
// `grpcio-tools` joined them on 2026-09-18: it carries protoc, which the wire
// guards and `scripts/regen_protos.py` need, and requirements.txt had claimed
// for months that the image installed it.
for pkg in ["grpcio", "protobuf", "grpcio-tools"] {
assert_eq!(
image_pin(pkg),
required(pkg),
"the image and py/requirements.txt disagree about `{pkg}`"
);
}
}
#[test]
fn the_image_asserts_the_version_it_installed() {
// The recipe must not merely `pip install streamlit==X` and hope. A pinned
// install that silently resolved to something else is the same shape as the
// `rustc --print target-list` hollow check the cargo toolchain image paid for.
let cf = facett_ops::holger::STREAMLIT_CONTAINERFILE;
assert!(
cf.contains("streamlit version"),
"the image must RUN streamlit and check the version it reports"
);
assert!(
cf.contains("FATAL: asked for streamlit"),
"the version check must fail by name, not silently"
);
}
#[test]
fn the_wheel_under_test_is_not_baked_into_the_image() {
// If `facett` or `facett-demo` were installed at image-build time, the running demo
// would prove nothing about the release — it would be testing a copy that never
// went through `publish-pip`. Both wheels arrive at RUN time (the built wheelhouse
// or an index). Pre-0.2.0 the artifact was `facet-wrapped-web`; still banned.
let cf = facett_ops::holger::STREAMLIT_CONTAINERFILE;
let install_lines: Vec<&str> =
cf.lines().filter(|l| l.contains("pip install")).collect();
for l in &install_lines {
assert!(
!l.contains("facet-wrapped-web") && !l.contains("facet_wrapped_web") && !l.contains("facett"),
"the image installs the artifact under test at BUILD time: {l}"
);
}
assert!(!install_lines.is_empty(), "the image installs nothing at all?");
}
#[test]
fn the_demo_binds_loopback_only() {
// MEASURED 2026-09-05, and it is why this test exists. The demo runs with
// `--network host` (it must, to reach holger on 127.0.0.1:8455), which means the
// container's interfaces ARE the host's. With streamlit's address left at
// 0.0.0.0 it announced:
//
// External URL: http://80.217.64.156:8501
//
// ...and the demo was reachable from the internet, on a development workstation,
// for as long as it ran. A demo is not a service. Nothing about it is
// authenticated, and the box it runs on holds every repo in the constellation.
//
// Both writers are checked: the image's default AND the command line, because
// either one alone would silently re-open it.
let cf = facett_ops::holger::STREAMLIT_CONTAINERFILE;
assert!(
!cf.contains("STREAMLIT_SERVER_ADDRESS=0.0.0.0"),
"the streamlit image defaults to binding every interface"
);
assert!(
cf.contains("STREAMLIT_SERVER_ADDRESS=127.0.0.1"),
"the streamlit image must default to loopback"
);
// The launch command is the other writer, and it wins over the env default.
let script = facett_ops::holger::streamlit_launch_command();
assert!(
script.contains("--server.address 127.0.0.1"),
"the launch command must pin the bind address explicitly: {script}"
);
assert!(
!script.contains("--server.address 0.0.0.0"),
"the launch command binds every interface: {script}"
);
}
#[test]
fn no_document_tells_anyone_to_bind_every_interface() {
// The exposure this lane hit was not a code bug — it was streamlit's DEFAULT plus
// `--network host`. But three documents in this repo also spelled
// `--server.address 0.0.0.0` as the command to run, so anyone following them
// reproduced it by hand, container or not.
//
// MEASURED on oden 2026-09-05: with 0.0.0.0 the demo announced
// `External URL: http://80.217.64.156:8501` and was reachable from the internet,
// unauthenticated, on a workstation holding every repo in the constellation.
//
// Guarding the CODE and leaving the instructions alone would have been the
// narrower half of the fix: a reader follows the doc, not the Containerfile.
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
let out = std::process::Command::new("git")
.current_dir(root)
.args(["ls-files"])
.output()
.expect("git ls-files");
let mut hits = Vec::new();
for rel in String::from_utf8_lossy(&out.stdout).lines() {
// This file names the pattern in order to ban it.
if rel.ends_with("streamlit_image_matches_requirements.rs") {
continue;
}
let Ok(text) = std::fs::read_to_string(root.join(rel)) else { continue };
for (i, line) in text.lines().enumerate() {
if line.contains("server.address 0.0.0.0")
|| line.contains("server.address=0.0.0.0")
|| line.contains("STREAMLIT_SERVER_ADDRESS=0.0.0.0")
{
hits.push(format!("{rel}:{}: {}", i + 1, line.trim()));
}
}
}
assert!(
hits.is_empty(),
"these tell a reader to bind the demo to every interface — on a box with a \
public IP that publishes an unauthenticated app to the internet:\n {}",
hits.join("\n ")
);
}