Skip to main content

greentic_setup/platform_setup/
persistence.rs

1//! Artifact persistence for static routes policy.
2
3use std::path::{Path, PathBuf};
4
5use anyhow::{Context, Result};
6use serde::Deserialize;
7
8use crate::platform_setup::types::{StaticRoutesPolicy, TelemetryAnswers, TunnelAnswers};
9
10/// Get the path to the static routes artifact file.
11pub fn static_routes_artifact_path(bundle_root: &Path) -> PathBuf {
12    bundle_root
13        .join("state")
14        .join("config")
15        .join("platform")
16        .join("static-routes.json")
17}
18
19/// Load static routes policy from the bundle artifact file.
20pub fn load_static_routes_artifact(bundle_root: &Path) -> Result<Option<StaticRoutesPolicy>> {
21    let path = static_routes_artifact_path(bundle_root);
22    if !path.exists() {
23        return Ok(None);
24    }
25    let raw = std::fs::read_to_string(&path)
26        .with_context(|| format!("failed to read {}", path.display()))?;
27    let policy = serde_json::from_str(&raw)
28        .or_else(|_| serde_yaml_bw::from_str(&raw))
29        .with_context(|| format!("failed to parse {}", path.display()))?;
30    Ok(Some(policy))
31}
32
33#[derive(Debug, Deserialize)]
34struct RuntimeEndpoints {
35    #[allow(dead_code)]
36    tenant: Option<String>,
37    #[allow(dead_code)]
38    team: Option<String>,
39    public_base_url: Option<String>,
40}
41
42/// Load public base URL from runtime endpoints file.
43pub fn load_runtime_public_base_url(
44    bundle_root: &Path,
45    tenant: &str,
46    team: Option<&str>,
47) -> Result<Option<String>> {
48    let team = team.unwrap_or("default");
49    let path = bundle_root
50        .join("state")
51        .join("runtime")
52        .join(format!("{tenant}.{team}"))
53        .join("endpoints.json");
54    if !path.exists() {
55        return Ok(None);
56    }
57    let raw = std::fs::read_to_string(&path)
58        .with_context(|| format!("failed to read {}", path.display()))?;
59    let endpoints: RuntimeEndpoints = serde_json::from_str(&raw)
60        .with_context(|| format!("failed to parse {}", path.display()))?;
61    Ok(endpoints
62        .public_base_url
63        .as_deref()
64        .map(str::trim)
65        .filter(|value| !value.is_empty())
66        .map(ToString::to_string))
67}
68
69/// Load effective static routes defaults, merging artifact and runtime data.
70pub fn load_effective_static_routes_defaults(
71    bundle_root: &Path,
72    tenant: &str,
73    team: Option<&str>,
74) -> Result<Option<StaticRoutesPolicy>> {
75    let mut policy = load_static_routes_artifact(bundle_root)?.unwrap_or_default();
76    if policy.public_base_url.is_none()
77        && let Some(runtime_url) = load_runtime_public_base_url(bundle_root, tenant, team)?
78    {
79        policy.public_base_url = Some(runtime_url);
80    }
81    if policy == StaticRoutesPolicy::disabled() {
82        return Ok(None);
83    }
84    Ok(Some(policy))
85}
86
87/// Get the path to the tunnel configuration artifact file.
88pub fn tunnel_artifact_path(bundle_root: &Path) -> PathBuf {
89    bundle_root.join(".greentic").join("tunnel.json")
90}
91
92/// Load tunnel answers from the bundle artifact file.
93pub fn load_tunnel_artifact(bundle_root: &Path) -> Result<Option<TunnelAnswers>> {
94    let path = tunnel_artifact_path(bundle_root);
95    if !path.exists() {
96        return Ok(None);
97    }
98    let raw = std::fs::read_to_string(&path)
99        .with_context(|| format!("failed to read {}", path.display()))?;
100    let answers: TunnelAnswers = serde_json::from_str(&raw)
101        .with_context(|| format!("failed to parse {}", path.display()))?;
102    Ok(Some(answers))
103}
104
105/// Persist tunnel answers to the bundle artifact file.
106pub fn persist_tunnel_artifact(bundle_root: &Path, answers: &TunnelAnswers) -> Result<PathBuf> {
107    let path = tunnel_artifact_path(bundle_root);
108    if let Some(parent) = path.parent() {
109        std::fs::create_dir_all(parent)?;
110    }
111    let payload = serde_json::to_string_pretty(answers).context("serialize tunnel answers")?;
112    std::fs::write(&path, payload)
113        .with_context(|| format!("failed to write {}", path.display()))?;
114    Ok(path)
115}
116
117/// Persist static routes policy to the bundle artifact file.
118pub fn persist_static_routes_artifact(
119    bundle_root: &Path,
120    policy: &StaticRoutesPolicy,
121) -> Result<PathBuf> {
122    let path = static_routes_artifact_path(bundle_root);
123    if let Some(parent) = path.parent() {
124        std::fs::create_dir_all(parent)?;
125    }
126    let payload = serde_json::to_string_pretty(policy).context("serialize static routes policy")?;
127    std::fs::write(&path, payload)
128        .with_context(|| format!("failed to write {}", path.display()))?;
129    Ok(path)
130}
131
132/// Get the path to the telemetry artifact file (sidecar to bundle.yaml).
133pub fn telemetry_artifact_path(bundle_root: &Path) -> PathBuf {
134    bundle_root
135        .join("state")
136        .join("config")
137        .join("platform")
138        .join("telemetry.json")
139}
140
141/// Load telemetry answers from the bundle artifact file.
142pub fn load_telemetry_artifact(bundle_root: &Path) -> Result<Option<TelemetryAnswers>> {
143    let path = telemetry_artifact_path(bundle_root);
144    if !path.exists() {
145        return Ok(None);
146    }
147    let raw = std::fs::read_to_string(&path)
148        .with_context(|| format!("failed to read {}", path.display()))?;
149    let answers: TelemetryAnswers = serde_json::from_str(&raw)
150        .with_context(|| format!("failed to parse {}", path.display()))?;
151    Ok(Some(answers))
152}
153
154/// Persist telemetry answers to the bundle artifact file.
155pub fn persist_telemetry_artifact(
156    bundle_root: &Path,
157    answers: &TelemetryAnswers,
158) -> Result<PathBuf> {
159    let path = telemetry_artifact_path(bundle_root);
160    if let Some(parent) = path.parent() {
161        std::fs::create_dir_all(parent)?;
162    }
163    let payload = serde_json::to_string_pretty(answers).context("serialize telemetry answers")?;
164    std::fs::write(&path, payload)
165        .with_context(|| format!("failed to write {}", path.display()))?;
166    Ok(path)
167}