fix(signoz): bind alert rules export shape
This commit is contained in:
@@ -26,7 +26,10 @@ TOKEN = "test-only-signoz-token-reference"
|
||||
|
||||
SOURCE_ASSETS: dict[str, Any] = {
|
||||
"/api/v1/dashboards": {"data": [{"id": "dash-1", "title": "Ops"}]},
|
||||
"/api/v1/rules": {"data": [{"id": "rule-1", "name": "Latency"}]},
|
||||
"/api/v1/rules": {
|
||||
"status": "success",
|
||||
"data": {"rules": [{"id": "rule-1", "name": "Latency"}]},
|
||||
},
|
||||
"/api/v1/explorer/views": {"data": [{"id": "view-1", "name": "Errors"}]},
|
||||
"/api/v1/roles": {"data": [{"id": "role-1", "name": "Viewer"}]},
|
||||
"/api/v1/org/preferences": {"data": [{"name": "theme", "value": "dark"}]},
|
||||
@@ -74,9 +77,16 @@ class MetadataHandler(BaseHTTPRequestHandler):
|
||||
self._send_json(401, {"error": "unauthorized"})
|
||||
return
|
||||
path = self.path.split("?", 1)[0]
|
||||
if path not in self.assets or not isinstance(
|
||||
self.assets[path].get("data"), list
|
||||
):
|
||||
if path not in self.assets:
|
||||
self._send_json(404, {"error": "not found"})
|
||||
return
|
||||
data = self.assets[path].get("data")
|
||||
collection = (
|
||||
data.get("rules")
|
||||
if path == "/api/v1/rules" and isinstance(data, dict)
|
||||
else data
|
||||
)
|
||||
if not isinstance(collection, list):
|
||||
self._send_json(404, {"error": "not found"})
|
||||
return
|
||||
length = int(self.headers.get("Content-Length", "0"))
|
||||
@@ -86,7 +96,7 @@ class MetadataHandler(BaseHTTPRequestHandler):
|
||||
return
|
||||
type(self).post_count += 1
|
||||
restored = {"id": f"server-{type(self).post_count}", **value}
|
||||
self.assets[path]["data"].append(restored)
|
||||
collection.append(restored)
|
||||
self._send_json(201, {"data": restored})
|
||||
|
||||
|
||||
@@ -254,6 +264,9 @@ def test_policy_is_explicitly_non_raw_and_non_completion() -> None:
|
||||
"/api/v1/domains",
|
||||
"/api/v1/channels",
|
||||
}
|
||||
alert_rules = next(item for item in policy["assets"] if item["id"] == "alert_rules")
|
||||
assert alert_rules["endpoint"] == "/api/v1/rules"
|
||||
assert alert_rules["response_pointer"] == "/data/rules"
|
||||
|
||||
|
||||
def test_check_mode_writes_nothing(tmp_path: Path) -> None:
|
||||
@@ -415,7 +428,7 @@ def test_restore_check_and_semantic_apply_are_bounded(tmp_path: Path) -> None:
|
||||
bundle, credential = create_bundle(tmp_path)
|
||||
target_assets = {
|
||||
"/api/v1/dashboards": {"data": []},
|
||||
"/api/v1/rules": {"data": []},
|
||||
"/api/v1/rules": {"status": "success", "data": {"rules": []}},
|
||||
"/api/v1/explorer/views": {"data": []},
|
||||
}
|
||||
with metadata_server(target_assets) as (target_base_url, handler):
|
||||
|
||||
@@ -17,6 +17,8 @@ SCP_TIMEOUT_SECONDS="${SIGNOZ_METADATA_SCP_TIMEOUT_SECONDS:-120}"
|
||||
REMOTE_TIMEOUT_SECONDS="${SIGNOZ_METADATA_REMOTE_TIMEOUT_SECONDS:-60}"
|
||||
KILL_AFTER_SECONDS="${SIGNOZ_METADATA_KILL_AFTER_SECONDS:-10}"
|
||||
LOCAL_PYTHON_BIN="${SIGNOZ_METADATA_LOCAL_PYTHON_BIN:-python3.11}"
|
||||
SSH_IDENTITY_FILE="${SIGNOZ_METADATA_SSH_IDENTITY_FILE:-}"
|
||||
SSH_KNOWN_HOSTS_FILE="${SIGNOZ_METADATA_KNOWN_HOSTS_FILE:-}"
|
||||
|
||||
LABELS=(
|
||||
metadata-export-policy.json
|
||||
@@ -35,11 +37,27 @@ LOCAL_SOURCES=(
|
||||
MODES=(0644 0644 0755 0755 0755)
|
||||
SSH_OPTIONS=(
|
||||
-o BatchMode=yes
|
||||
-o IdentitiesOnly=yes
|
||||
-o StrictHostKeyChecking=yes
|
||||
-o ConnectTimeout=8
|
||||
-o ConnectionAttempts=1
|
||||
-o ServerAliveInterval=5
|
||||
-o ServerAliveCountMax=2
|
||||
)
|
||||
if [ -n "${SSH_IDENTITY_FILE}" ]; then
|
||||
[ -f "${SSH_IDENTITY_FILE}" ] && [ ! -L "${SSH_IDENTITY_FILE}" ] || {
|
||||
echo "invalid SSH identity file" >&2
|
||||
exit 64
|
||||
}
|
||||
SSH_OPTIONS+=( -i "${SSH_IDENTITY_FILE}" )
|
||||
fi
|
||||
if [ -n "${SSH_KNOWN_HOSTS_FILE}" ]; then
|
||||
[ -f "${SSH_KNOWN_HOSTS_FILE}" ] && [ ! -L "${SSH_KNOWN_HOSTS_FILE}" ] || {
|
||||
echo "invalid SSH known_hosts file" >&2
|
||||
exit 64
|
||||
}
|
||||
SSH_OPTIONS+=( -o "UserKnownHostsFile=${SSH_KNOWN_HOSTS_FILE}" )
|
||||
fi
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
@@ -50,6 +68,10 @@ Required environment:
|
||||
RUN_ID Unique path-safe execution identifier
|
||||
WORK_ITEM_ID Must be P0-OBS-002
|
||||
|
||||
Optional fixed transport paths:
|
||||
SIGNOZ_METADATA_SSH_IDENTITY_FILE
|
||||
SIGNOZ_METADATA_KNOWN_HOSTS_FILE
|
||||
|
||||
--check validates the five local sources and performs a no-write host110 diff.
|
||||
--apply creates one immutable exact-hash directory. It does not create or
|
||||
change an active pointer and does not run an authenticated metadata export.
|
||||
|
||||
@@ -31,7 +31,7 @@ def test_entrypoint_is_fixed_to_windows99_host110_and_p0_obs_002() -> None:
|
||||
assert "$result.entrypointHashMatched" in source
|
||||
assert '$CredentialFile = "/etc/awoooi/secrets/signoz-metadata-api-key"' in source
|
||||
assert (
|
||||
'$BundleId = "bb6d78b67f506072b2e45e92bc1d415364e25852282a9b068900665c578df011"'
|
||||
'$BundleId = "337b5f86921d9b0af1a49e3bfe3e16c6a359f8fdfa1521c9dcbf230582ec286e"'
|
||||
in source
|
||||
)
|
||||
assert '$BaseUrl = "http://127.0.0.1:8080"' in source
|
||||
|
||||
@@ -86,11 +86,18 @@ printf 'scp %s\n' "$*" >> "${TEST_EVENT_LOG:?}"
|
||||
|
||||
|
||||
def run_deployer(
|
||||
tmp_path: Path, *, mode: str, apply: bool
|
||||
tmp_path: Path,
|
||||
*,
|
||||
mode: str,
|
||||
apply: bool,
|
||||
env_overrides: dict[str, str] | None = None,
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
env = fake_environment(tmp_path, mode=mode)
|
||||
if env_overrides:
|
||||
env.update(env_overrides)
|
||||
return subprocess.run(
|
||||
["bash", str(DEPLOYER), "--apply" if apply else "--check"],
|
||||
env=fake_environment(tmp_path, mode=mode),
|
||||
env=env,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
@@ -165,6 +172,65 @@ def test_deployer_has_no_active_pointer_or_destructive_fallback() -> None:
|
||||
assert "ln -s" not in source
|
||||
|
||||
|
||||
def test_transport_propagates_strict_fixed_paths_with_spaces(tmp_path: Path) -> None:
|
||||
transport_dir = tmp_path / "transport paths"
|
||||
transport_dir.mkdir()
|
||||
identity = transport_dir / "agent99 identity"
|
||||
known_hosts = transport_dir / "known hosts"
|
||||
identity.write_text("test-only-identity\n", encoding="utf-8")
|
||||
known_hosts.write_text("test-only-known-host\n", encoding="utf-8")
|
||||
|
||||
result = run_deployer(
|
||||
tmp_path,
|
||||
mode="check_absent",
|
||||
apply=False,
|
||||
env_overrides={
|
||||
"SIGNOZ_METADATA_SSH_IDENTITY_FILE": str(identity),
|
||||
"SIGNOZ_METADATA_KNOWN_HOSTS_FILE": str(known_hosts),
|
||||
},
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
event = (tmp_path / "events.log").read_text(encoding="utf-8")
|
||||
assert "IdentitiesOnly=yes" in event
|
||||
assert "StrictHostKeyChecking=yes" in event
|
||||
assert str(identity) in event
|
||||
assert f"UserKnownHostsFile={known_hosts}" in event
|
||||
|
||||
|
||||
def test_transport_rejects_missing_fixed_path_before_remote_use(tmp_path: Path) -> None:
|
||||
result = run_deployer(
|
||||
tmp_path,
|
||||
mode="check_absent",
|
||||
apply=False,
|
||||
env_overrides={
|
||||
"SIGNOZ_METADATA_SSH_IDENTITY_FILE": str(tmp_path / "missing identity")
|
||||
},
|
||||
)
|
||||
|
||||
assert result.returncode == 64
|
||||
assert "invalid SSH identity file" in result.stderr
|
||||
assert not (tmp_path / "events.log").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_transport_rejects_symlink_fixed_path_before_remote_use(tmp_path: Path) -> None:
|
||||
target = tmp_path / "known-hosts-target"
|
||||
target.write_text("test-only-known-host\n", encoding="utf-8")
|
||||
symlink = tmp_path / "known hosts link"
|
||||
symlink.symlink_to(target)
|
||||
|
||||
result = run_deployer(
|
||||
tmp_path,
|
||||
mode="check_absent",
|
||||
apply=False,
|
||||
env_overrides={"SIGNOZ_METADATA_KNOWN_HOSTS_FILE": str(symlink)},
|
||||
)
|
||||
|
||||
assert result.returncode == 64
|
||||
assert "invalid SSH known_hosts file" in result.stderr
|
||||
assert not (tmp_path / "events.log").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_shared_operation_lock_is_existing_read_only_and_fail_closed() -> None:
|
||||
source = DEPLOYER.read_text(encoding="utf-8")
|
||||
assert "OPERATION_LOCK=/tmp/awoooi-signoz-backup-operation.lock" in source
|
||||
|
||||
@@ -858,6 +858,9 @@ def evaluate(root: Path, contract_path: Path) -> dict[str, Any]:
|
||||
"signoz_sqlite_application_consistent_backup", {}
|
||||
)
|
||||
metadata_export_source_contract = sqlite_backup.get("source_contract", {})
|
||||
metadata_candidate_deployment = metadata_export_source_contract.get(
|
||||
"candidate_deployment", {}
|
||||
)
|
||||
metadata_deployment = metadata_export_source_contract.get("deployment", {})
|
||||
declared_metadata_export_hashes = metadata_export_source_contract.get("hashes", {})
|
||||
actual_metadata_export_hashes = _current_metadata_export_source_hashes(root)
|
||||
@@ -866,7 +869,7 @@ def evaluate(root: Path, contract_path: Path) -> dict[str, Any]:
|
||||
== len(METADATA_EXPORT_SOURCE_PATHS)
|
||||
and set(declared_metadata_export_hashes) == set(METADATA_EXPORT_SOURCE_PATHS)
|
||||
and declared_metadata_export_hashes == actual_metadata_export_hashes
|
||||
and metadata_export_source_contract.get("test_terminal") == "21_passed"
|
||||
and metadata_export_source_contract.get("test_terminal") == "24_passed"
|
||||
)
|
||||
offsite_backup = pending_verifiers.get("signoz_backup_offsite_dr", {})
|
||||
failed_artifacts = pending_verifiers.get(
|
||||
@@ -889,7 +892,8 @@ def evaluate(root: Path, contract_path: Path) -> dict[str, Any]:
|
||||
== "pending_supported_non_raw_export_or_coordinated_snapshot"
|
||||
and sqlite_backup.get("raw_sqlite_read_or_sync_allowed") is False
|
||||
and sqlite_backup.get("sqlite_cli_present_in_runtime") is False
|
||||
and sqlite_backup.get("source_contract_status") == "deployed_inactive_verified"
|
||||
and sqlite_backup.get("source_contract_status")
|
||||
== "source_candidate_pending_deployment"
|
||||
and metadata_export_source_contract.get("policy")
|
||||
== "config/signoz/metadata-export-policy.json"
|
||||
and metadata_export_source_contract.get("exporter")
|
||||
@@ -902,6 +906,18 @@ def evaluate(root: Path, contract_path: Path) -> dict[str, Any]:
|
||||
and metadata_export_source_contract.get("raw_volume_read") is False
|
||||
and metadata_export_source_contract.get("secret_value_persistence") is False
|
||||
and metadata_export_source_contract.get("full_sqlite_completion_claim") is False
|
||||
and metadata_candidate_deployment.get("status")
|
||||
== "pending_exact_check_apply_postcheck"
|
||||
and metadata_candidate_deployment.get("asset_id")
|
||||
== "host110-signoz-metadata-toolchain"
|
||||
and metadata_candidate_deployment.get("target_host") == "192.168.0.110"
|
||||
and metadata_candidate_deployment.get("bundle_id")
|
||||
== "337b5f86921d9b0af1a49e3bfe3e16c6a359f8fdfa1521c9dcbf230582ec286e"
|
||||
and metadata_candidate_deployment.get("target_path")
|
||||
== "/backup/toolchains/signoz-metadata/337b5f86921d9b0af1a49e3bfe3e16c6a359f8fdfa1521c9dcbf230582ec286e"
|
||||
and metadata_candidate_deployment.get("active_pointer_present") is False
|
||||
and metadata_candidate_deployment.get("runtime_export_executed") is False
|
||||
and metadata_candidate_deployment.get("completion_claim") is False
|
||||
and metadata_deployment.get("status") == "deployed_inactive_verified"
|
||||
and metadata_deployment.get("asset_id") == "host110-signoz-metadata-toolchain"
|
||||
and metadata_deployment.get("target_host") == "192.168.0.110"
|
||||
|
||||
@@ -834,7 +834,9 @@ def test_post_release_verifier_and_native_backup_close_with_remaining_gaps() ->
|
||||
]
|
||||
assert sqlite_pending["raw_sqlite_read_or_sync_allowed"] is False
|
||||
assert sqlite_pending["sqlite_cli_present_in_runtime"] is False
|
||||
assert sqlite_pending["source_contract_status"] == ("deployed_inactive_verified")
|
||||
assert sqlite_pending["source_contract_status"] == (
|
||||
"source_candidate_pending_deployment"
|
||||
)
|
||||
source_contract = sqlite_pending["source_contract"]
|
||||
assert source_contract["policy"] == "config/signoz/metadata-export-policy.json"
|
||||
assert source_contract["exporter"] == ("scripts/backup/signoz-metadata-export.py")
|
||||
@@ -875,7 +877,17 @@ def test_post_release_verifier_and_native_backup_close_with_remaining_gaps() ->
|
||||
field: hashlib.sha256((ROOT / relative).read_bytes()).hexdigest()
|
||||
for field, relative in metadata_paths.items()
|
||||
}
|
||||
assert source_contract["test_terminal"] == "21_passed"
|
||||
assert source_contract["test_terminal"] == "24_passed"
|
||||
candidate = source_contract["candidate_deployment"]
|
||||
assert candidate["status"] == "pending_exact_check_apply_postcheck"
|
||||
assert candidate["asset_id"] == "host110-signoz-metadata-toolchain"
|
||||
assert candidate["target_host"] == "192.168.0.110"
|
||||
assert candidate["bundle_id"] == (
|
||||
"337b5f86921d9b0af1a49e3bfe3e16c6a359f8fdfa1521c9dcbf230582ec286e"
|
||||
)
|
||||
assert candidate["active_pointer_present"] is False
|
||||
assert candidate["runtime_export_executed"] is False
|
||||
assert candidate["completion_claim"] is False
|
||||
deployment = source_contract["deployment"]
|
||||
assert deployment["status"] == "deployed_inactive_verified"
|
||||
assert deployment["asset_id"] == "host110-signoz-metadata-toolchain"
|
||||
@@ -1094,7 +1106,7 @@ def test_post_closure_contract_mirror_and_program_blockers_are_consistent() -> N
|
||||
post_closure["signoz_sqlite_application_consistent_backup_status"] == "pending"
|
||||
)
|
||||
assert post_closure["signoz_metadata_export_source_contract_status"] == (
|
||||
"deployed_inactive_verified"
|
||||
"source_candidate_pending_deployment"
|
||||
)
|
||||
assert post_closure["signoz_metadata_export_policy"] == (
|
||||
"config/signoz/metadata-export-policy.json"
|
||||
|
||||
Reference in New Issue
Block a user