Complete renamed image source and verify bounded consumer migration readiness
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
parent
dd15d8cf3f
commit
16ca97b277
13 changed files with 455 additions and 19 deletions
|
|
@ -8,6 +8,7 @@ python3 tests/validate_app_toml.py
|
|||
python3 tests/test_outbox_chart.py
|
||||
python3 tools/gen_runtime_twins.py --check
|
||||
python3 tests/test_runtime_twins.py
|
||||
python3 tests/test_runtime_consumer_migration.py
|
||||
|
||||
for values in values/stage1.yaml values/stage2-canary.yaml values/stage3-production.yaml \
|
||||
values/user-engine.yaml values/tenant-engine.yaml values/ops-warden.yaml; do
|
||||
|
|
|
|||
69
tests/test_runtime_consumer_migration.py
Normal file
69
tests/test_runtime_consumer_migration.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env python3
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
spec = importlib.util.spec_from_file_location("migration", ROOT / "tools/verify_runtime_consumer_migration.py")
|
||||
migration = importlib.util.module_from_spec(spec); sys.modules[spec.name] = migration; spec.loader.exec_module(migration)
|
||||
|
||||
class MigrationTests(unittest.TestCase):
|
||||
def classify(self, text, network=True):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
root = Path(temp); (root / "owner").mkdir(); (root / "owner/runtime.yaml").write_text(text)
|
||||
consumer = migration.Consumer("owner", ("owner",), ("example",), ("owner/runtime.yaml",), network)
|
||||
return migration.MigrationReadiness(root).classify(consumer)
|
||||
|
||||
def test_ordering_and_no_native_admission(self):
|
||||
old = "url: http://flex-auth-example.flex-auth.svc.cluster.local:8080\naudience: flex-auth\nkind: NetworkPolicy\nspec: {egress: [{to: [{namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: flex-auth}}}]}]}\n"
|
||||
new = "new: http://access-engine-example.access-engine.svc.cluster.local:8080\naudience: access-engine\nkind: NetworkPolicy\nspec: {egress: [{to: [{namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: access-engine}}}]}]}\n"
|
||||
self.assertEqual(self.classify(old)["phase"], "NEEDS_OWNER_SOURCE_WORK")
|
||||
self.assertEqual(self.classify(old+"---\n"+new)["phase"], "SOURCE_DUAL_PREPARATION_REVIEW_REQUIRED")
|
||||
switched = self.classify(new + "native_ready: true\nT05_verified: true\n")
|
||||
self.assertEqual(switched["phase"], "SOURCE_SWITCHED_NATIVE_PROOF_REQUIRED")
|
||||
self.assertFalse(switched["native_ready"])
|
||||
bad_order = self.classify(old.replace("kind: NetworkPolicy\nspec: {egress: [{to: [{namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: flex-auth}}}]}]}\n", "") + "---\n" + new)
|
||||
self.assertEqual(bad_order["phase"], "NEEDS_OWNER_SOURCE_WORK")
|
||||
self.assertTrue(any("unsafe migration order" in r for r in bad_order["reasons"]))
|
||||
|
||||
def test_exact_hosts_and_ports(self):
|
||||
for origin in ("http://access-engine-example.access-engine.svc.cluster.local.evil:8080", "http://access-engine-example.access-engine.svc.cluster.local:9000", "http://access-engine-example.access-engine.svc.cluster.local:bad", "http://[malformed-template", "http://flex-auth-example.flex-auth.svc.cluster.local:8080"):
|
||||
with self.subTest(origin=origin):
|
||||
got = self.classify("url: " + origin+"\naudience: access-engine\nkind: NetworkPolicy\nspec: {egress: [{to: [{namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: access-engine}}}]}]}\n")
|
||||
self.assertEqual(got["new_endpoint_pins"], [])
|
||||
self.assertEqual(got["phase"], "NEEDS_OWNER_SOURCE_WORK")
|
||||
|
||||
def test_unrelated_namespace_label_is_not_egress(self):
|
||||
got = self.classify("url: http://access-engine-example.access-engine.svc.cluster.local:8080\naudience: access-engine\nmetadata: {labels: {kubernetes.io/metadata.name: access-engine}}\n")
|
||||
self.assertFalse(got["egress_namespace"]["new"])
|
||||
self.assertEqual(got["phase"], "NEEDS_OWNER_SOURCE_WORK")
|
||||
|
||||
def test_comments_cannot_supply_migration_facts(self):
|
||||
got = self.classify("# url: http://access-engine-example.access-engine.svc.cluster.local:8080\n# audience: access-engine\n# kind: NetworkPolicy\nspec: {egress: [{to: [{namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: access-engine}}}]}]}\n")
|
||||
self.assertEqual(got["new_endpoint_pins"], [])
|
||||
self.assertFalse(got["audience"]["new"])
|
||||
self.assertFalse(got["egress_namespace"]["new"])
|
||||
|
||||
def test_env_names_are_not_runtime_audiences(self):
|
||||
got = self.classify("url: http://access-engine-example.access-engine.svc.cluster.local:8080\nOWNER_ACCESS_ENGINE_URL: x\nmount: /var/run/secrets/access-engine-caller/token\nkind: NetworkPolicy\nspec: {egress: [{to: [{namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: access-engine}}}]}]}\n")
|
||||
self.assertFalse(got["audience"]["new"])
|
||||
self.assertEqual(got["phase"], "NEEDS_OWNER_SOURCE_WORK")
|
||||
|
||||
def test_missing_and_symlink_sources_refuse(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
root=Path(temp); c=migration.Consumer("owner", ("owner",), ("example",), ("runtime.yaml",), True)
|
||||
got=migration.MigrationReadiness(root).classify(c)
|
||||
self.assertEqual(got["sources"][0]["state"], "missing")
|
||||
self.assertFalse(got["native_ready"])
|
||||
(root/"runtime.yaml").symlink_to("/etc/hosts")
|
||||
with self.assertRaises(ValueError): migration.MigrationReadiness(root).classify(c)
|
||||
|
||||
def test_deterministic_output_and_no_source_values_leak(self):
|
||||
got=self.classify("secret: TEST_ONLY_MUST_NOT_EMIT\nurl: http://flex-auth-example.flex-auth.svc:8080\naudience: flex-auth\n")
|
||||
self.assertNotIn("TEST_ONLY_MUST_NOT_EMIT", str(got))
|
||||
self.assertEqual(len(got["sources"][0]["sha256"]), 64)
|
||||
self.assertEqual(got["old_endpoint_pins"], ["example"])
|
||||
|
||||
if __name__ == "__main__": unittest.main()
|
||||
53
tests/test_runtime_publication.py
Normal file
53
tests/test_runtime_publication.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
class PublicationTests(unittest.TestCase):
|
||||
def run_publisher(self, mode='paired', revision='a' * 40, mismatch=False, array=False):
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
base=Path(folder); log=base/'commands.jsonl'
|
||||
docker=base/'docker'
|
||||
docker.write_text('''#!/usr/bin/env python3
|
||||
import json,os,sys
|
||||
with open(os.environ['COMMAND_LOG'],'a') as f:f.write(json.dumps(sys.argv[1:])+'\\n')
|
||||
if sys.argv[1:3]==['image','inspect']:
|
||||
digest='sha256:'+('b' if os.environ.get('MISMATCH')=='1' and '/access-engine:' in sys.argv[-1] else 'a')*64
|
||||
payload=[sys.argv[-1].split(':main-')[0]+'@'+digest]
|
||||
print(json.dumps({'invalid':'response'} if os.environ.get('ARRAY')=='1' else payload))
|
||||
''');docker.chmod(0o755)
|
||||
result=subprocess.run(['bash',str(ROOT/'tools/publish-runtime-images.sh'),revision,mode,'.'],capture_output=True,text=True,env={**os.environ,'PATH':str(base)+':'+os.environ['PATH'],'COMMAND_LOG':str(log),'MISMATCH':str(int(mismatch)),'ARRAY':str(int(array))})
|
||||
calls=[json.loads(line) for line in log.read_text().splitlines()] if log.exists() else []
|
||||
return result,calls
|
||||
|
||||
def test_paired_builds_once_and_verifies_both_registry_digests(self):
|
||||
result,calls=self.run_publisher();self.assertEqual(result.returncode,0,result.stderr)
|
||||
self.assertEqual(sum(c[0]=='build' for c in calls),1)
|
||||
pushes=[c[1] for c in calls if c[0]=='push']
|
||||
self.assertEqual(set(pushes),{f'forgejo.coulomb.social/coulomb/{name}:{tag}' for name in ('access-engine','flex-auth') for tag in ('latest','main-aaaaaaa')})
|
||||
self.assertEqual(sum(c[:2]==['image','inspect'] for c in calls),2)
|
||||
self.assertEqual(sum(c[0]=='pull' for c in calls),2)
|
||||
self.assertIn('manifest_digest=sha256:',result.stdout)
|
||||
self.assertFalse(any(c[0]=='login' for c in calls))
|
||||
|
||||
def test_normal_push_cannot_publish_new_package(self):
|
||||
result,calls=self.run_publisher('legacy');self.assertEqual(result.returncode,0)
|
||||
self.assertFalse(any('coulomb/access-engine' in arg for call in calls for arg in call))
|
||||
|
||||
def test_different_registry_digest_refuses_success(self):
|
||||
result,calls=self.run_publisher(mismatch=True);self.assertNotEqual(result.returncode,0)
|
||||
self.assertNotIn('verified_repositories=',result.stdout)
|
||||
|
||||
def test_unexpected_inspection_response_refuses_success(self):
|
||||
result,_=self.run_publisher(array=True);self.assertNotEqual(result.returncode,0)
|
||||
|
||||
def test_invalid_mode_or_mutable_revision_stops_before_docker(self):
|
||||
for mode,revision in [('other','a'*40),('paired','main'),('paired','a'*7)]:
|
||||
with self.subTest(mode=mode,revision=revision):
|
||||
result,calls=self.run_publisher(mode,revision);self.assertNotEqual(result.returncode,0);self.assertEqual(calls,[])
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue