157 lines
8.6 KiB
Python
157 lines
8.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Linux CI contracts using real staging/tar/shell and a Buildah boundary fake."""
|
|
import importlib.util
|
|
import fcntl
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tarfile
|
|
import tempfile
|
|
import time
|
|
import unittest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
class LinuxBuildTests(unittest.TestCase):
|
|
def test_retired_store_cleanup_preserves_recent_locked_and_unrelated_directories(self):
|
|
spec = importlib.util.spec_from_file_location('retired_stores', ROOT / 'ci/cleanup-retired-stores.py')
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
base = Path(directory)
|
|
names = ['dev-image-build-noble-aot-v1-probe-amd64',
|
|
'dev-image-build-noble-aot-v1-agent-arm64',
|
|
'dev-node-index-agent', 'linux-agent', 'unrelated']
|
|
for name in names:
|
|
metadata = base / name / '.local/share/containers/storage/overlay-images/images.json'
|
|
metadata.parent.mkdir(parents=True)
|
|
metadata.write_text('[]')
|
|
if name != names[1]:
|
|
old = time.time() - 8 * 86400
|
|
os.utime(metadata, (old, old))
|
|
with (base / names[2] / '.lock').open('a') as lock:
|
|
fcntl.flock(lock, fcntl.LOCK_EX)
|
|
self.assertEqual(module.cleanup(base), [names[0]])
|
|
self.assertFalse((base / names[0]).exists())
|
|
for name in names[1:]:
|
|
self.assertTrue((base / name).is_dir())
|
|
|
|
def test_restore_context_contains_only_the_executables_dependency_graph_and_is_stable(self):
|
|
spec = importlib.util.spec_from_file_location('stage_node', ROOT / 'ci/stage-node-image.py')
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
base = Path(directory)
|
|
first, second = base / 'first', base / 'second'
|
|
module.stage('agent', 'linux-x64', first)
|
|
module.stage('agent', 'linux-musl-arm64', second)
|
|
actual = {p.relative_to(first / 'restore/backend').as_posix()
|
|
for p in (first / 'restore/backend').rglob('*.csproj')}
|
|
expected = {'agent/src/MoShiro.Agent/MoShiro.Agent.csproj',
|
|
'fleet/tools/fleet-enroll/fleet-enroll.csproj',
|
|
'shared/MoShiro.Measurement/MoShiro.Measurement.csproj',
|
|
'shared/MoShiro.Protocol/MoShiro.Protocol.csproj'}
|
|
self.assertTrue(expected <= actual)
|
|
self.assertFalse(any('/MoShiro.Backend' in name or '/tests/' in name for name in actual))
|
|
left = {p.relative_to(first / 'restore'): (p.read_bytes(), p.stat().st_mtime)
|
|
for p in (first / 'restore').rglob('*') if p.is_file()}
|
|
right = {p.relative_to(second / 'restore'): (p.read_bytes(), p.stat().st_mtime)
|
|
for p in (second / 'restore').rglob('*') if p.is_file()}
|
|
self.assertEqual(left, right)
|
|
self.assertTrue(all(mtime == module.FIXED_MTIME for _, mtime in left.values()))
|
|
self.assertTrue((second / 'source/packaging/native/msquic/linux-musl-arm64/libmsquic.a').is_file())
|
|
traversal = (first / 'source/backend/Agent.Native.proj').read_text()
|
|
self.assertIn('Targets="Restore" BuildInParallel="false"', traversal)
|
|
self.assertIn('Targets="Build" BuildInParallel="false"', traversal)
|
|
self.assertIn('RuntimeIdentifier=$(RuntimeIdentifier);SelfContained=$(SelfContained)', traversal)
|
|
containerfile = (first / 'Containerfile').read_text()
|
|
self.assertIn('test -n "$NODE_RID"', containerfile)
|
|
self.assertIn('-p:RuntimeIdentifier="$NODE_RID"', containerfile)
|
|
|
|
def test_retention_removes_only_expired_layers_without_pruning_package_caches(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
base = Path(directory)
|
|
buildah = base / 'buildah'
|
|
buildah.write_text('''#!/usr/bin/env bash
|
|
set -eu
|
|
case "$1" in
|
|
images) printf 'old %s <none>:<none>\nrecent %s <none>:<none>\nversion %s localhost/moshiro-agent:1.0.0-amd64\nrestore %s localhost/moshiro-agent-restore:0123456789abcdef0123456789abcdef01234567-linux-x64\nactive %s localhost/moshiro-agent:ci-dev-amd64\n' "$OLD_TIME" "$RECENT_TIME" "$OLD_TIME" "$OLD_TIME" "$OLD_TIME" ;;
|
|
rmi) printf '%s\n' "$*" >> "$FIXTURE/removed" ;;
|
|
unshare) exit 0 ;;
|
|
*) exit 9 ;;
|
|
esac
|
|
''')
|
|
buildah.chmod(0o755)
|
|
env = dict(os.environ, PATH=str(base) + ':' + os.environ['PATH'], FIXTURE=str(base),
|
|
OLD_TIME=str(int(time.time()) - 8 * 86400), RECENT_TIME=str(int(time.time())),
|
|
CONTAINERS_STORAGE_CONF=str(base / 'moshiro-storage.conf'))
|
|
subprocess.run(['bash', str(ROOT / 'ci/cleanup-build-cache.sh')], env=env, check=True, capture_output=True)
|
|
self.assertEqual((base / 'removed').read_text(), 'rmi localhost/moshiro-agent-binaries:linux-musl-x64\nrmi old\nrmi version\nrmi restore\n')
|
|
|
|
def test_export_requires_one_unshare_session_and_cleans_up_on_copy_failure(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
base = Path(directory)
|
|
(base / 'bin').mkdir()
|
|
(base / 'image/out/agent').mkdir(parents=True)
|
|
(base / 'image/out/enroll').mkdir(parents=True)
|
|
(base / 'image/out/agent/moshiro-agent').write_bytes(b'agent')
|
|
enroll = base / 'image/out/enroll/fleet-enroll'
|
|
enroll.write_bytes(b'enroll')
|
|
buildah = base / 'bin/buildah'
|
|
buildah.write_text('''#!/usr/bin/env bash
|
|
set -eu
|
|
if [[ "$1" == unshare ]]; then
|
|
shift
|
|
export TEST_UNSHARE=yes
|
|
exec "$@"
|
|
fi
|
|
[[ "${TEST_UNSHARE:-}" == yes ]] || exit 125
|
|
case "$1" in
|
|
from) echo test-container ;;
|
|
mount) echo "$FIXTURE/image" ;;
|
|
unmount|rm) echo "$1" >> "$FIXTURE/cleanup" ;;
|
|
*) exit 2 ;;
|
|
esac
|
|
''')
|
|
buildah.chmod(0o755)
|
|
env = dict(os.environ, PATH=str(base / 'bin') + ':' + os.environ['PATH'], FIXTURE=str(base))
|
|
command = ['bash', str(ROOT / 'ci/extract-node-binaries.sh'), 'test-image', str(base / 'output')]
|
|
for succeeds in (True, False):
|
|
with self.subTest(succeeds=succeeds):
|
|
if not succeeds:
|
|
enroll.unlink()
|
|
result = subprocess.run(command, env=env, capture_output=True, text=True)
|
|
self.assertEqual(result.returncode == 0, succeeds, result.stderr)
|
|
self.assertEqual((base / 'cleanup').read_text().splitlines(), ['unmount', 'rm'])
|
|
(base / 'cleanup').unlink()
|
|
if succeeds:
|
|
self.assertEqual((base / 'output/moshiro-agent').read_bytes(), b'agent')
|
|
self.assertEqual((base / 'output/fleet-enroll').read_bytes(), b'enroll')
|
|
|
|
def test_linux_packages_preserve_installers_and_shared_library_payloads(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
base = Path(directory)
|
|
binaries = base / 'binaries'
|
|
binaries.mkdir()
|
|
for name in ('moshiro-agent', 'fleet-enroll', 'libmsquic.so.2'):
|
|
(binaries / name).write_bytes(name.encode())
|
|
for rid, suffix in [('linux-x64', 'amd64'), ('linux-arm64', 'arm64'),
|
|
('linux-musl-arm64', 'musl-arm64')]:
|
|
with self.subTest(rid=rid):
|
|
output = base / rid
|
|
env = dict(os.environ, CI_COMMIT_TAG='v1.2.3')
|
|
subprocess.run(['bash', str(ROOT / 'ci/package-linux-agent.sh'), rid, str(binaries), str(output)], env=env, check=True, capture_output=True)
|
|
archive = output / f'moshiro-agent-v1.2.3-linux-{suffix}.tar.gz'
|
|
with tarfile.open(archive) as package:
|
|
for name in ('moshiro-agent', 'fleet-enroll', 'libmsquic.so.2', 'install.sh',
|
|
'systemd/moshiro-agent.service', 'openwrt/moshiro-agent.init', 'LICENSE'):
|
|
self.assertIn(name, package.getnames())
|
|
self.assertEqual(package.extractfile('moshiro-agent').read(), b'moshiro-agent')
|
|
self.assertEqual(package.getmember('moshiro-agent').mode, 0o755)
|
|
subprocess.run(['sha256sum', '-c', archive.name + '.sha256'], cwd=output, check=True, capture_output=True)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main(verbosity=2)
|