isoloom_render/
backend.rs1use isoloom_manifest::Manifest;
6use isoloom_plan::{Capabilities, Environment, refusal};
7use isoloom_spec::model::Target;
8
9use crate::{Error, GeneratedFile, Result};
10
11pub type Files = Vec<GeneratedFile>;
13
14pub trait Backend: Sync {
16 fn target(&self) -> Target;
18
19 fn capabilities(&self) -> Capabilities;
21
22 fn render(&self, env: &Environment) -> Result<Files>;
29
30 fn refusal(&self, env: &Environment) -> Option<String> {
33 refusal(&env.requirements, &self.capabilities()).map(|r| r.to_string())
34 }
35
36 fn run_manifest(&self, _env: &Environment) -> Result<Option<Manifest>> {
44 Ok(None)
45 }
46
47 fn run_manifests(&self, env: &Environment) -> Result<Vec<Placed>> {
56 Ok(self.run_manifest(env)?.into_iter().map(Placed::here).collect())
57 }
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct Placed {
63 pub sub: Option<&'static str>,
65 pub manifest: Manifest,
67}
68
69impl Placed {
70 #[must_use]
72 pub fn here(manifest: Manifest) -> Placed {
73 Placed { sub: None, manifest }
74 }
75
76 #[must_use]
78 pub fn under(sub: &'static str, manifest: Manifest) -> Placed {
79 Placed { sub: Some(sub), manifest }
80 }
81}
82
83pub fn check(target: Target, env: &Environment, caps: &Capabilities) -> Result<()> {
89 match refusal(&env.requirements, caps) {
90 Some(refusal) => Err(Error::Refused { target, refusal }),
91 None => Ok(()),
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use isoloom_plan::{Capabilities, Feature, Requirement};
98 use isoloom_spec::model::Target;
99
100 use super::{Backend, Files, check};
101 use crate::{Error, Result, testing};
102
103 struct Bare;
105
106 impl Backend for Bare {
107 fn target(&self) -> Target {
108 Target::Kubernetes
109 }
110
111 fn capabilities(&self) -> Capabilities {
112 Capabilities::of([])
113 }
114
115 fn render(&self, _env: &isoloom_plan::Environment) -> Result<Files> {
116 Ok(Vec::new())
117 }
118 }
119
120 const ALIASED: &str =
121 "version: 1\nname: t\nnetworks: { lan: { cidr: 10.9.0.0/24 } }\nmachines:\n web: { networks: { lan: 10 }, aliases: [www], app: { image: nginx } }\n";
122
123 #[test]
124 fn a_backend_refuses_the_first_requirement_it_lacks_and_says_why() {
125 let env = testing::env(ALIASED, Target::Kubernetes);
126 let refused = check(Target::Kubernetes, &env, &Capabilities::of([]));
127 assert!(
128 matches!(&refused, Err(Error::Refused { target: Target::Kubernetes, refusal }) if refusal.requirement == Requirement::MachineAliases),
129 "{refused:?}"
130 );
131 assert_eq!(check(Target::Kubernetes, &env, &Capabilities::of([Feature::MachineAliases])), Ok(()));
132 assert_eq!(
133 Bare.refusal(&env).as_deref(),
134 Some("machine `aliases` (dotted DNS names) aren't Kubernetes Service names")
135 );
136 assert_eq!(Bare.run_manifest(&env), Ok(None));
137 assert_eq!(Bare.run_manifests(&env), Ok(Vec::new()));
138 }
139}