Reconcile reef bindings and evidence
This commit is contained in:
parent
721398125b
commit
3e98b10d81
15 changed files with 541 additions and 36 deletions
86
tools/check_binding_projection.py
Executable file
86
tools/check_binding_projection.py
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Check reef binding files against authoritative sibling declarations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
def load_yaml(path: Path) -> dict:
|
||||
value = yaml.safe_load(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError(f"{path}: expected a YAML mapping")
|
||||
return value
|
||||
|
||||
|
||||
def ids(items: object, key: str) -> set[str]:
|
||||
if not isinstance(items, list):
|
||||
return set()
|
||||
return {
|
||||
item[key]
|
||||
for item in items
|
||||
if isinstance(item, dict) and isinstance(item.get(key), str)
|
||||
}
|
||||
|
||||
|
||||
def expected_rapps(parent: Path, reef_id: str) -> set[str]:
|
||||
result: set[str] = set()
|
||||
for declaration_path in sorted(parent.glob("rapp-*/declarations/rapp.yaml")):
|
||||
declaration = load_yaml(declaration_path)
|
||||
if reef_id in declaration.get("bound_reefs", []):
|
||||
result.add(str(declaration["rapp_id"]))
|
||||
return result
|
||||
|
||||
|
||||
def expected_rails(parent: Path, declared: list[str]) -> set[str]:
|
||||
result: set[str] = set()
|
||||
for rail_id in declared:
|
||||
declaration_path = parent / rail_id / "declarations" / "rail.yaml"
|
||||
declaration = load_yaml(declaration_path)
|
||||
result.add(str(declaration["rail_id"]))
|
||||
return result
|
||||
|
||||
|
||||
def assess(repo: Path, parent: Path) -> list[str]:
|
||||
reef = load_yaml(repo / "declarations" / "reef.yaml")
|
||||
reef_id = str(reef["reef_id"])
|
||||
rapp_bindings = load_yaml(repo / "bindings" / "rapps.yaml")
|
||||
rail_bindings = load_yaml(repo / "bindings" / "rails.yaml")
|
||||
|
||||
actual_rapps = ids(rapp_bindings.get("bound_rapps"), "rapp_id")
|
||||
derived_rapps = expected_rapps(parent, reef_id)
|
||||
actual_rails = ids(rail_bindings.get("hosted_rails"), "rail_id")
|
||||
derived_rails = expected_rails(parent, list(reef.get("hosted_rails", [])))
|
||||
|
||||
failures: list[str] = []
|
||||
if actual_rapps != derived_rapps:
|
||||
failures.append(
|
||||
f"rapp projection differs: binding={sorted(actual_rapps)}, "
|
||||
f"derived={sorted(derived_rapps)}"
|
||||
)
|
||||
if actual_rails != derived_rails:
|
||||
failures.append(
|
||||
f"rail projection differs: binding={sorted(actual_rails)}, "
|
||||
f"declared={sorted(derived_rails)}"
|
||||
)
|
||||
return failures
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--repo", type=Path, default=Path(__file__).parents[1])
|
||||
parser.add_argument("--parent", type=Path)
|
||||
args = parser.parse_args()
|
||||
repo = args.repo.resolve()
|
||||
parent = (args.parent or repo.parent).resolve()
|
||||
failures = assess(repo, parent)
|
||||
print(json.dumps({"pass": not failures, "failures": failures}))
|
||||
return bool(failures)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
85
tools/collect_rail_runtime_evidence.sh
Executable file
85
tools/collect_rail_runtime_evidence.sh
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Read-only, secret-free evidence collector for the rails hosted by this reef.
|
||||
# The cluster API is intentionally reached through the operator SSH path; 6443
|
||||
# remains non-public. Override only when the approved executor name changes.
|
||||
remote_host="${RAIL_RUNTIME_HOST:-railiance01}"
|
||||
|
||||
ssh -o BatchMode=yes "$remote_host" 'bash -s' <<'REMOTE'
|
||||
set -euo pipefail
|
||||
|
||||
context="$(kubectl config current-context)"
|
||||
server_version="$(kubectl version -o json | jq -r '.serverVersion.gitVersion')"
|
||||
nodes="$(kubectl get nodes -o json)"
|
||||
knative_deployments="$(kubectl -n knative-serving get deployments -o json)"
|
||||
kourier_service="$(kubectl -n kourier-system get service kourier -o json)"
|
||||
knative_services="$(kubectl get ksvc -A -o json)"
|
||||
ingress_classes="$(kubectl get ingressclass -o json)"
|
||||
storage_classes="$(kubectl get storageclass -o json)"
|
||||
network_policy_count="$(kubectl get networkpolicy -A -o json | jq '.items | length')"
|
||||
|
||||
jq -n \
|
||||
--arg context "$context" \
|
||||
--arg server_version "$server_version" \
|
||||
--argjson nodes "$nodes" \
|
||||
--argjson deployments "$knative_deployments" \
|
||||
--argjson kourier "$kourier_service" \
|
||||
--argjson services "$knative_services" \
|
||||
--argjson ingress_classes "$ingress_classes" \
|
||||
--argjson storage_classes "$storage_classes" \
|
||||
--argjson network_policy_count "$network_policy_count" \
|
||||
'{
|
||||
schema_version: "reef-railiance.rail-runtime-evidence/v1",
|
||||
collected_at: (now | todateiso8601),
|
||||
collector: "reef-railiance/tools/collect_rail_runtime_evidence.sh",
|
||||
executor: "operator-ssh",
|
||||
context: $context,
|
||||
kubernetes: {
|
||||
server_version: $server_version,
|
||||
nodes: {
|
||||
total: ($nodes.items | length),
|
||||
ready: ([$nodes.items[] | select(any(.status.conditions[]; .type == "Ready" and .status == "True"))] | length),
|
||||
members: [$nodes.items[] | {
|
||||
name: .metadata.name,
|
||||
roles: ([.metadata.labels | keys[] | select(startswith("node-role.kubernetes.io/")) | split("/")[1]] | sort),
|
||||
kubelet_version: .status.nodeInfo.kubeletVersion,
|
||||
allocatable: {
|
||||
cpu: .status.allocatable.cpu,
|
||||
memory: .status.allocatable.memory,
|
||||
pods: .status.allocatable.pods
|
||||
}
|
||||
}]
|
||||
},
|
||||
ingress_classes: [$ingress_classes.items[].metadata.name] | sort,
|
||||
storage_classes: [$storage_classes.items[].metadata.name] | sort,
|
||||
network_policy_count: $network_policy_count
|
||||
},
|
||||
knative: {
|
||||
deployments: [$deployments.items[] | {
|
||||
name: .metadata.name,
|
||||
desired: (.spec.replicas // 0),
|
||||
available: (.status.availableReplicas // 0),
|
||||
images: [.spec.template.spec.containers[].image]
|
||||
}] | sort_by(.name),
|
||||
all_deployments_available: (all($deployments.items[]; (.status.availableReplicas // 0) >= (.spec.replicas // 0))),
|
||||
ingress: {
|
||||
implementation: "net-kourier",
|
||||
service_type: $kourier.spec.type,
|
||||
cluster_ip: $kourier.spec.clusterIP
|
||||
},
|
||||
services: [$services.items[] | {
|
||||
namespace: .metadata.namespace,
|
||||
name: .metadata.name,
|
||||
latest_created_revision: .status.latestCreatedRevisionName,
|
||||
latest_ready_revision: .status.latestReadyRevisionName,
|
||||
ready: (any(.status.conditions[]?; .type == "Ready" and .status == "True"))
|
||||
}] | sort_by(.namespace, .name)
|
||||
},
|
||||
failure_domain: {
|
||||
independent_members_observed: ($nodes.items | length),
|
||||
result: (if ($nodes.items | length) > 1 then "requires-source-backed-review" else "fail-single-node" end)
|
||||
},
|
||||
secret_values_collected: false
|
||||
}'
|
||||
REMOTE
|
||||
Loading…
Add table
Add a link
Reference in a new issue