seaplane_cli/context/
formation.rs1use std::collections::HashSet;
2
3use seaplane::api::{
4 compute::v1::FormationConfiguration as FormationConfigurationModel,
5 shared::v1::{Provider as ProviderModel, Region as RegionModel},
6};
7
8use crate::{
9 cli::{cmds::formation::SeaplaneFormationPlanArgMatches, Provider, Region},
10 context::Ctx,
11 error::{CliError, CliErrorKind, Context, Result},
12 ops::{flight::Flights, formation::Endpoint, generate_formation_name},
13 printer::Color,
14};
15
16fn no_matching_flight(flight: &str) -> CliError {
17 CliErrorKind::NoMatchingItem(flight.to_string())
18 .into_err()
19 .context("(hint: create the Flight Plan with '")
20 .with_color_context(|| (Color::Green, format!("seaplane flight plan {flight}")))
21 .context("')\n")
22 .context("(hint: or try fetching remote definitions with '")
23 .color_context(Color::Green, "seaplane formation fetch-remote")
24 .context("')\n")
25}
26
27#[derive(Debug, Clone)]
37pub struct FormationCtx {
38 pub name_id: String,
39 pub launch: bool,
40 pub remote: bool,
41 pub local: bool,
42 pub grounded: bool,
43 pub recursive: bool,
44 pub cfg_ctx: FormationCfgCtx,
46}
47
48impl Default for FormationCtx {
49 fn default() -> Self {
50 Self {
51 name_id: generate_formation_name(),
52 launch: false,
53 cfg_ctx: FormationCfgCtx::default(),
54 remote: false,
55 local: true,
56 grounded: false,
57 recursive: false,
58 }
59 }
60}
61
62impl FormationCtx {
63 pub fn update_from_formation_plan(
65 &mut self,
66 matches: &SeaplaneFormationPlanArgMatches,
67 flights_db: &Flights,
68 ) -> Result<()> {
69 let matches = matches.0;
70 let mut flight_names = Vec::new();
73
74 for flight in matches
76 .get_many::<String>("include-flight-plan")
77 .unwrap_or_default()
78 .filter(|f| !(f.starts_with('@') || f.contains('=')))
80 {
81 if let Some(flight) = flights_db.find_name_or_partial_id(flight) {
83 flight_names.push(flight.model.name().to_owned());
85 } else {
86 #[cfg(not(any(feature = "ui_tests", feature = "semantic_ui_tests",)))]
87 {
88 return Err(no_matching_flight(flight));
90 }
91 }
92 }
93
94 self.name_id = matches
95 .get_one::<String>("name_id")
96 .map(ToOwned::to_owned)
97 .unwrap_or_else(generate_formation_name);
98
99 self.grounded = matches.get_flag("grounded");
100 self.launch = matches.get_flag("launch");
101 self.cfg_ctx
102 .flights
103 .extend(flight_names.iter().map(|s| s.to_string()));
104 self.cfg_ctx.affinities.extend(
105 matches
106 .get_many::<String>("affinity")
107 .unwrap_or_default()
108 .map(ToOwned::to_owned),
109 );
110 self.cfg_ctx.connections.extend(
111 matches
112 .get_many::<String>("connection")
113 .unwrap_or_default()
114 .map(ToOwned::to_owned),
115 );
116 self.cfg_ctx.providers_allowed = matches
117 .get_many::<Provider>("provider")
118 .unwrap_or_default()
119 .filter_map(Provider::into_model)
120 .collect();
121 self.cfg_ctx.providers_denied = matches
122 .get_many::<Provider>("exclude-provider")
123 .unwrap_or_default()
124 .filter_map(Provider::into_model)
125 .collect();
126 self.cfg_ctx.regions_allowed = matches
127 .get_many::<Region>("region")
128 .unwrap_or_default()
129 .filter_map(Region::into_model)
130 .collect();
131 self.cfg_ctx.regions_denied = matches
132 .get_many::<Region>("exclude-region")
133 .unwrap_or_default()
134 .filter_map(Region::into_model)
135 .collect();
136 self.cfg_ctx.public_endpoints = matches
137 .get_many::<Endpoint>("public-endpoint")
138 .unwrap_or_default()
139 .cloned()
140 .collect();
141 self.cfg_ctx.formation_endpoints = matches
142 .get_many::<Endpoint>("formation-endpoint")
143 .unwrap_or_default()
144 .cloned()
145 .collect();
146 self.cfg_ctx.flight_endpoints = matches
147 .get_many::<Endpoint>("flight-endpoint")
148 .unwrap_or_default()
149 .cloned()
150 .collect();
151 Ok(())
152 }
153
154 pub fn configuration_model(&self, ctx: &Ctx) -> Result<FormationConfigurationModel> {
156 let mut f_model = FormationConfigurationModel::builder();
158
159 for flight_name in &self.cfg_ctx.flights {
160 let flight = ctx
161 .db
162 .flights
163 .find_name(flight_name)
164 .ok_or_else(|| no_matching_flight(flight_name))?;
165 f_model = f_model.add_flight(flight.model.clone());
166 }
167
168 for &item in &self.cfg_ctx.providers_allowed {
170 f_model = f_model.add_allowed_provider(item);
171 }
172 for &item in &self.cfg_ctx.providers_denied {
173 f_model = f_model.add_denied_provider(item);
174 }
175 for &item in &self.cfg_ctx.regions_allowed {
176 f_model = f_model.add_allowed_region(item);
177 }
178 for &item in &self.cfg_ctx.regions_denied {
179 f_model = f_model.add_denied_region(item);
180 }
181 for item in &self.cfg_ctx.public_endpoints {
182 f_model = f_model.add_public_endpoint(item.key(), item.value());
183 }
184 for item in &self.cfg_ctx.flight_endpoints {
185 f_model = f_model.add_flight_endpoint(item.key(), item.value());
186 }
187 #[cfg(feature = "unstable")]
188 {
189 for item in &self.cfg_ctx.affinities {
190 f_model = f_model.add_affinity(item);
191 }
192 for item in &self.cfg_ctx.connections {
193 f_model = f_model.add_connection(item);
194 }
195 for item in &self.cfg_ctx.formation_endpoints {
196 f_model = f_model.add_formation_endpoint(item.key(), item.value());
197 }
198 }
199
200 f_model.build().map_err(Into::into)
202 }
203}
204
205#[derive(Default, Debug, Clone)]
206pub struct FormationCfgCtx {
207 pub flights: Vec<String>,
209 pub affinities: Vec<String>,
211 pub connections: Vec<String>,
213 pub providers_allowed: HashSet<ProviderModel>,
215 pub providers_denied: HashSet<ProviderModel>,
217 pub regions_allowed: HashSet<RegionModel>,
219 pub regions_denied: HashSet<RegionModel>,
221 pub public_endpoints: Vec<Endpoint>,
222 pub formation_endpoints: Vec<Endpoint>,
223 pub flight_endpoints: Vec<Endpoint>,
224}