#![allow(clippy::module_name_repetitions)]
use std::collections::{BTreeMap, BTreeSet};
pub use lava_core::{Architecture, ResourceRef, Value};
pub use lava_eval::{
eval_architecture, eval_architecture_with_schema, interfaces_in_source, parse, Atom,
EvalError, InputBindings, InterfaceParseError, ParseError, Sx,
};
pub use lava_schema::{Field, Interface, SchemaError};
pub use lava_types::Type;
pub const BUNDLED_ARCHITECTURES: &[(&str, usize)] = &[
("aws-vpc-network", 10), ("cloudflare-dns-records", 5), ("discord-server-baseline", 3), ("pleme-io-server", 44), ("akeyless-secrets", 6), ("cloudflare-r2-bucket", 4), ("public-dns", 6), ("akeyless-platform", 8), ("cloudflare-tunnel", 3), ("cloudflare-zone", 2), ("aws-eks-cluster", 4), ("split-horizon-dns", 4), ("dns-record-set", 2), ("cilium-irsa", 3), ("cluster-autoscaler-iam", 2), ("backup-recovery", 3), ("ami-production-iam", 3), ("cloudflare-tunnel-ingress", 3), ("cloudflare-headless-blog", 3), ("akeyless-aws-integration", 3), ("convergence-dashboard", 4), ("drill-network", 3), ("cloudflare-zero-trust-access", 2), ("akeyless-dev-cluster", 2), ("ami-test-iam", 2), ("drill-iam-role", 2), ("continuous-monitoring", 3), ("akeyless-target-attested-infra", 2), ("cloudflare-domain", 2), ("cloudflare-dns-security", 3), ("aws-sg-ingress-rules", 2), ("github-org-repos", 1), ("akeyless-dev-packer", 2), ("akeyless-dev-workspace", 2), ("azure-aks-cluster", 1), ("burst-forge-dashboard", 2), ("dns-record-set-typed", 4), ("backup-recovery-multi-region", 3), ("cilium-irsa-variants", 3), ];
#[must_use]
pub fn interface_for(name: &str) -> Option<Interface> {
let path = bundled_source_path(name);
let src = std::fs::read_to_string(&path).ok()?;
let ifaces = interfaces_in_source(&src).ok()?;
ifaces.into_iter().find(|i| i.name == name)
}
fn bundled_source_path(name: &str) -> std::path::PathBuf {
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join(ARCHITECTURE_DIR)
.join(format!("{name}.tlisp"))
}
#[must_use]
pub fn bundled_source(name: &str) -> Option<String> {
std::fs::read_to_string(bundled_source_path(name)).ok()
}
pub const ARCHITECTURE_DIR: &str = "architectures";
pub fn load_bundled(
name: &str,
bindings: &InputBindings,
) -> Result<lava_core::Architecture, EvalError> {
let path = bundled_source_path(name);
let src = std::fs::read_to_string(&path)
.map_err(|e| EvalError::NotArchForm(format!("io: {} — {e}", path.display())))?;
eval_architecture(&src, bindings)
}
pub const DASHBOARD_DIR: &str = "dashboards";
const LEGEND_LB: &str = "\u{0}lava-lb\u{0}";
const LEGEND_RB: &str = "\u{0}lava-rb\u{0}";
fn mask_legends(src: &str) -> String {
src.replace("{{", LEGEND_LB).replace("}}", LEGEND_RB)
}
fn unmask_legends(src: &str) -> String {
src.replace(LEGEND_LB, "{{").replace(LEGEND_RB, "}}")
}
fn strip_comments(src: &str) -> String {
let mut out = String::with_capacity(src.len());
let mut chars = src.chars();
let mut in_string = false;
let mut escaped = false;
while let Some(c) = chars.next() {
if in_string {
out.push(c);
if escaped {
escaped = false;
} else if c == '\\' {
escaped = true;
} else if c == '"' {
in_string = false;
}
} else if c == '"' {
in_string = true;
out.push(c);
} else if c == ';' {
for c2 in chars.by_ref() {
if c2 == '\n' {
out.push('\n');
break;
}
}
} else {
out.push(c);
}
}
out
}
fn scan_placeholders(masked: &str, out: &mut BTreeSet<String>) {
let b = masked.as_bytes();
let mut i = 0;
while i < b.len() {
if b[i] == b'{' {
let start = i + 1;
let mut j = start;
if j < b.len() && (b[j].is_ascii_alphabetic() || b[j] == b'_') {
j += 1;
while j < b.len() && (b[j].is_ascii_alphanumeric() || b[j] == b'_') {
j += 1;
}
if j < b.len() && b[j] == b'}' {
out.insert(masked[start..j].to_string());
i = j + 1;
continue;
}
}
}
i += 1;
}
}
#[must_use]
pub fn required_dashboard_params(src: &str) -> BTreeSet<String> {
let mut out = BTreeSet::new();
scan_placeholders(&mask_legends(&strip_comments(src)), &mut out);
out
}
#[must_use]
pub fn bind_dashboard_params<K, V>(src: &str, params: &BTreeMap<K, V>) -> String
where
K: AsRef<str>,
V: AsRef<str>,
{
let mut out = mask_legends(src);
for (k, v) in params {
let k = k.as_ref();
let mut needle = String::with_capacity(k.len() + 2);
needle.push('{');
needle.push_str(k);
needle.push('}');
out = out.replace(&needle, v.as_ref());
}
unmask_legends(&out)
}
#[must_use]
pub fn unbound_placeholders(rendered: &str) -> BTreeSet<String> {
let mut out = BTreeSet::new();
scan_placeholders(&mask_legends(rendered), &mut out);
out
}
#[cfg(test)]
mod dashboard_param_tests {
use super::*;
#[test]
fn prose_braces_in_comments_are_not_required_params() {
let src = ";; bound as {placeholders}\n(deflava-dashboard d :uid \"{env}\")";
let req = required_dashboard_params(src);
assert!(req.contains("env"));
assert!(
!req.contains("placeholders"),
"a brace in prose became a required param: {req:?}"
);
}
#[test]
fn a_semicolon_inside_a_string_does_not_start_a_comment() {
let src = "(deflava-dashboard d :description \"a; b\" :uid \"{job}\")";
assert!(
required_dashboard_params(src).contains("job"),
"a placeholder after an in-string semicolon was dropped"
);
}
#[test]
fn selectors_are_not_mistaken_for_placeholders() {
let src = r#"(:expr "{namespace=\"camelot\"} up{job=\"auth\"}")"#;
assert!(
required_dashboard_params(src).is_empty(),
"a query selector was read as a placeholder"
);
}
#[test]
fn legends_survive_binding_and_are_not_reported_as_leaks() {
let params = BTreeMap::from([("namespace", "camelot")]);
let bound = bind_dashboard_params("{namespace} {{namespace}}", ¶ms);
assert_eq!(bound, "camelot {{namespace}}");
assert!(
unbound_placeholders(&bound).is_empty(),
"a preserved Grafana legend was reported as an unbound placeholder"
);
}
#[test]
fn an_unbound_placeholder_is_named() {
let params = BTreeMap::from([("env", "camelot")]);
let bound = bind_dashboard_params("{env}-{service}", ¶ms);
let leaked = unbound_placeholders(&bound);
assert_eq!(leaked.len(), 1);
assert!(leaked.contains("service"), "got {leaked:?}");
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
#[test]
fn aws_vpc_network_tlisp_renders_byte_equivalent_to_pangea_spec() {
let bindings = InputBindings::new();
let arch = load_bundled("aws-vpc-network", &bindings).unwrap();
let json = arch.render_terraform_json().unwrap();
assert_eq!(json["resource"]["aws_vpc"]["main-vpc"]["cidr_block"], "10.0.0.0/16");
assert_eq!(json["resource"]["aws_vpc"]["main-vpc"]["enable_dns_support"], true);
assert_eq!(json["resource"]["aws_vpc"]["main-vpc"]["enable_dns_hostnames"], true);
assert_eq!(json["resource"]["aws_vpc"]["main-vpc"]["tags"]["Name"], "main-vpc");
assert_eq!(
json["resource"]["aws_vpc"]["main-vpc"]["tags"]["Environment"],
"production"
);
assert_eq!(
json["resource"]["aws_internet_gateway"]["main-igw"]["vpc_id"],
"${aws_vpc.main-vpc.id}"
);
for i in 0..3 {
let name = format!("main-public-{i}");
assert_eq!(
json["resource"]["aws_subnet"][&name]["vpc_id"],
"${aws_vpc.main-vpc.id}",
"public subnet {i} vpc_id"
);
assert_eq!(
json["resource"]["aws_subnet"][&name]["cidr_block"],
format!("10.0.{i}.0/24"),
"public subnet {i} cidr"
);
assert_eq!(
json["resource"]["aws_subnet"][&name]["map_public_ip_on_launch"],
true,
"public subnet {i} map_public_ip"
);
}
for i in 0..3 {
let name = format!("main-private-{i}");
assert_eq!(
json["resource"]["aws_subnet"][&name]["vpc_id"],
"${aws_vpc.main-vpc.id}",
"private subnet {i} vpc_id"
);
assert_eq!(
json["resource"]["aws_subnet"][&name]["cidr_block"],
format!("10.0.{}.0/24", i + 10),
"private subnet {i} cidr"
);
}
assert_eq!(json["resource"]["aws_eip"]["main-nat-eip"]["domain"], "vpc");
assert_eq!(
json["resource"]["aws_nat_gateway"]["main-nat"]["subnet_id"],
"${aws_subnet.main-public-0.id}"
);
assert_eq!(
json["resource"]["aws_nat_gateway"]["main-nat"]["allocation_id"],
"${aws_eip.main-nat-eip.allocation_id}"
);
assert_eq!(
json["resource"]["aws_security_group"]["main-default-sg"]["vpc_id"],
"${aws_vpc.main-vpc.id}"
);
}
#[test]
fn cloudflare_dns_records_tlisp_renders_five_typed_records() {
let bindings = InputBindings::new();
let arch = load_bundled("cloudflare-dns-records", &bindings).unwrap();
let json = arch.render_terraform_json().unwrap();
let records = &json["resource"]["cloudflare_dns_record"];
assert_eq!(records["root-cname"]["name"], "@");
assert_eq!(records["root-cname"]["type"], "CNAME");
assert_eq!(records["root-cname"]["proxied"], true);
assert_eq!(records["www-cname"]["name"], "www");
assert_eq!(records["api-cname"]["proxied"], true);
assert_eq!(records["wildcard-staging-cname"]["name"], "*.staging");
assert_eq!(records["wildcard-staging-cname"]["proxied"], false);
assert_eq!(records["acme-challenge-txt"]["type"], "TXT");
assert_eq!(records["acme-challenge-txt"]["name"], "_acme-challenge");
}
#[test]
fn akeyless_secrets_tlisp_renders_full_resource_set() {
let bindings = InputBindings::new();
let arch = load_bundled("akeyless-secrets", &bindings).unwrap();
let json = arch.render_terraform_json().unwrap();
assert_eq!(
json["resource"]["akeyless_auth_method_api_key"]["prod-ci-auth"]["name"],
"/prod/ci-auth"
);
assert_eq!(
json["resource"]["akeyless_auth_method_k8s"]["prod-k8s-auth"]["name"],
"/prod/k8s-auth"
);
assert_eq!(
json["resource"]["akeyless_static_secret"]["prod-db-password"]["name"],
"/prod/database/password"
);
assert_eq!(
json["resource"]["akeyless_static_secret"]["prod-api-key"]["name"],
"/prod/service/api-key"
);
assert_eq!(
json["resource"]["akeyless_target_db"]["prod-db-target"]["db_type"],
"postgres"
);
assert_eq!(
json["resource"]["akeyless_role"]["prod-app-role"]["name"],
"/prod/roles/app"
);
}
#[test]
fn cloudflare_interface_rejects_missing_required_zone_id() {
let iface = interface_for("cloudflare-dns-records").unwrap();
let src = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join(ARCHITECTURE_DIR)
.join("cloudflare-dns-records.tlisp"),
)
.unwrap();
let bindings = InputBindings::new();
let err = eval_architecture_with_schema(&src, &bindings, &iface).unwrap_err();
match err {
EvalError::Schema {
interface, errors, ..
} => {
assert_eq!(interface, "cloudflare-dns-records");
assert!(errors.iter().any(|e| matches!(e, SchemaError::MissingRequired { name, .. } if name == "zone-id")));
}
other => panic!("expected EvalError::Schema, got {other:?}"),
}
}
#[test]
fn akeyless_interface_gates_valid_eval() {
let iface = interface_for("akeyless-secrets").unwrap();
let src = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join(ARCHITECTURE_DIR)
.join("akeyless-secrets.tlisp"),
)
.unwrap();
let mut bindings = InputBindings::new();
bindings.set_str("name-prefix", "staging");
let arch = eval_architecture_with_schema(&src, &bindings, &iface).unwrap();
let json = arch.render_terraform_json().unwrap();
assert_eq!(
json["resource"]["akeyless_role"]["staging-app-role"]["name"],
"/staging/roles/app"
);
}
#[test]
fn user_inputs_override_defaults_in_tlisp_eval() {
let mut b = InputBindings::new();
b.set_str("name", "preview");
b.set_list("availability-zones", vec!["us-west-2a".into(), "us-west-2b".into()]);
let arch = load_bundled("aws-vpc-network", &b).unwrap();
let json = arch.render_terraform_json().unwrap();
assert!(json["resource"]["aws_vpc"]["preview-vpc"].is_object());
assert!(json["resource"]["aws_internet_gateway"]["preview-igw"].is_object());
assert!(json["resource"]["aws_subnet"]["preview-public-0"].is_object());
assert!(json["resource"]["aws_subnet"]["preview-public-1"].is_object());
assert!(json["resource"]["aws_subnet"]["preview-public-2"].is_null());
assert_eq!(
json["resource"]["aws_subnet"]["preview-public-0"]["availability_zone"],
"us-west-2a"
);
assert_eq!(
json["resource"]["aws_subnet"]["preview-public-1"]["availability_zone"],
"us-west-2b"
);
}
}