203 lines
6.4 KiB
YAML
203 lines
6.4 KiB
YAML
# Invoke through python3 docker/verify.py, never with the production Compose file.
|
|
# The verifier supplies immutable IDs for these already-built local images.
|
|
name: ${TRUF_WORKER_TEST_PROJECT:?Invoke python3 docker/verify.py}
|
|
|
|
x-isolated: &isolated
|
|
pull_policy: never
|
|
read_only: true
|
|
user: "10001:10001"
|
|
cap_drop: [ALL]
|
|
security_opt: [no-new-privileges:true]
|
|
network_mode: none
|
|
init: false
|
|
# Override Docker client proxy defaults without importing host credentials.
|
|
environment:
|
|
HTTP_PROXY: ""
|
|
http_proxy: ""
|
|
HTTPS_PROXY: ""
|
|
https_proxy: ""
|
|
FTP_PROXY: ""
|
|
ftp_proxy: ""
|
|
ALL_PROXY: ""
|
|
all_proxy: ""
|
|
NO_PROXY: "*"
|
|
no_proxy: "*"
|
|
tmpfs:
|
|
- /run/truf:rw,nosuid,nodev,noexec,size=64m,mode=0700,uid=10001,gid=10001
|
|
- /tmp:rw,nosuid,nodev,noexec,size=128m,mode=1777
|
|
cpus: 2.0
|
|
mem_limit: 6g
|
|
pids_limit: 512
|
|
shm_size: 256m
|
|
stop_signal: SIGTERM
|
|
stop_grace_period: 600s
|
|
restart: "no"
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "16m"
|
|
max-file: "4"
|
|
|
|
x-runtime: &runtime
|
|
<<: *isolated
|
|
image: ${TRUF_WORKER_TEST_RUNTIME_IMAGE:?Invoke python3 docker/verify.py}
|
|
volumes:
|
|
- type: volume
|
|
source: data
|
|
target: /data
|
|
|
|
services:
|
|
tools:
|
|
<<: *isolated
|
|
image: ${TRUF_WORKER_TEST_TEST_IMAGE:?Invoke python3 docker/verify.py}
|
|
volumes:
|
|
# Only the test tree is copied up, never the test image's application.
|
|
- type: volume
|
|
source: tools
|
|
target: /opt/truf/tests
|
|
volume:
|
|
nocopy: false
|
|
entrypoint: [/usr/local/bin/python3, -I, -S, -B, -c]
|
|
command:
|
|
- |
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import stat
|
|
|
|
try:
|
|
assert os.getuid() == os.geteuid() == os.getgid() == 10001
|
|
root = Path('/opt/truf/tests')
|
|
fixture_files = {
|
|
root / 'fixtures/worker_tls_cert.pem',
|
|
root / 'fixtures/worker_tls_key.pem',
|
|
}
|
|
pending = [root]
|
|
files = []
|
|
size = 0
|
|
entries = 0
|
|
while pending:
|
|
path = pending.pop()
|
|
entries += 1
|
|
assert entries <= 512
|
|
details = path.lstat()
|
|
assert (details.st_uid, details.st_gid) == (10001, 10001)
|
|
if stat.S_ISDIR(details.st_mode):
|
|
assert stat.S_IMODE(details.st_mode) == 0o700
|
|
pending.extend(path.iterdir())
|
|
assert len(pending) + len(files) <= 512
|
|
else:
|
|
assert stat.S_ISREG(details.st_mode)
|
|
assert stat.S_IMODE(details.st_mode) == 0o600
|
|
assert (path.suffix == '.py' or path in fixture_files)
|
|
assert details.st_size <= 4 * 1024 * 1024
|
|
files.append(path)
|
|
size += details.st_size
|
|
assert size <= 64 * 1024 * 1024
|
|
assert root / 'container_e2e.py' in files
|
|
assert fixture_files <= set(files)
|
|
tree = hashlib.sha256()
|
|
for path in sorted(files):
|
|
tree.update(path.relative_to(root).as_posix().encode('utf-8') + b'\0')
|
|
tree.update(hashlib.sha256(path.read_bytes()).digest())
|
|
summary = {
|
|
'counts': {'ok': 1, 'tool_files': len(files), 'tool_bytes': size},
|
|
'hashes': {
|
|
'tools_tree_sha256': tree.hexdigest(),
|
|
'driver_sha256': hashlib.sha256((root / 'container_e2e.py').read_bytes()).hexdigest(),
|
|
},
|
|
}
|
|
except Exception:
|
|
summary = {'counts': {'ok': 0, 'tools_seed_failed': 1}, 'hashes': {}}
|
|
print(json.dumps(summary, sort_keys=True))
|
|
raise SystemExit(0 if summary['counts']['ok'] else 1)
|
|
|
|
provision:
|
|
<<: *runtime
|
|
user: "0:0"
|
|
cap_add: [CHOWN, DAC_OVERRIDE, FOWNER]
|
|
command: [provision]
|
|
|
|
prepare:
|
|
<<: *runtime
|
|
volumes:
|
|
- type: volume
|
|
source: data
|
|
target: /data
|
|
- type: volume
|
|
source: tools
|
|
target: /opt/truf/tests
|
|
read_only: true
|
|
volume:
|
|
nocopy: true
|
|
entrypoint: [/usr/local/bin/python3, -I, -S, -B, /opt/truf/tests/container_e2e.py]
|
|
command: [prepare, --config, /data/config/e2e.yaml]
|
|
|
|
runtime:
|
|
<<: *runtime
|
|
volumes:
|
|
- type: volume
|
|
source: data
|
|
target: /data
|
|
- type: volume
|
|
source: tools
|
|
target: /opt/truf/tests
|
|
read_only: true
|
|
volume:
|
|
nocopy: true
|
|
# Preserve the production image's tini -> container_runtime entrypoint.
|
|
# Do not add Compose init, a shell supervisor, or a test-image server.
|
|
command: [run, --config, /data/config/e2e.yaml]
|
|
healthcheck:
|
|
test: [CMD, /usr/local/bin/python3, -I, -S, -B, /opt/truf/app/container_runtime.py, health, --config, /data/config/e2e.yaml]
|
|
interval: 5s
|
|
timeout: 15s
|
|
start_period: 240s
|
|
retries: 3
|
|
|
|
stopped:
|
|
<<: *runtime
|
|
volumes:
|
|
- type: volume
|
|
source: data
|
|
target: /data
|
|
read_only: true
|
|
volume:
|
|
nocopy: true
|
|
entrypoint: [/usr/local/bin/python3, -I, -S, -B, -c]
|
|
command:
|
|
- |
|
|
import json
|
|
import os
|
|
import runpy
|
|
|
|
try:
|
|
runtime = runpy.run_path('/opt/truf/app/container_runtime.py')
|
|
runtime['require_container']()
|
|
runtime['private_path']('/data/postgres-linux', directory=True)
|
|
marker = runtime['_read_json'](runtime['INITIALIZED'])
|
|
assert marker.get('pg_major') == 16
|
|
try:
|
|
os.lstat('/data/postgres-linux/postmaster.pid')
|
|
except FileNotFoundError:
|
|
pass
|
|
else:
|
|
raise AssertionError
|
|
summary = {'counts': {
|
|
'ok': 1, 'private_postgres_directory': 1,
|
|
'postgres_pid_absent': 1, 'initialized': 1,
|
|
}, 'hashes': {}}
|
|
except Exception:
|
|
summary = {'counts': {'ok': 0, 'stopped_data_check_failed': 1}, 'hashes': {}}
|
|
print(json.dumps(summary, sort_keys=True))
|
|
raise SystemExit(0 if summary['counts']['ok'] else 1)
|
|
|
|
volumes:
|
|
data:
|
|
name: ${TRUF_WORKER_TEST_PROJECT:?Invoke python3 docker/verify.py}_data
|
|
driver: local
|
|
tools:
|
|
name: ${TRUF_WORKER_TEST_PROJECT:?Invoke python3 docker/verify.py}_tools
|
|
driver: local
|