1use anyhow::Result;
6use cm_rust::{ConfigNestedValueType, ConfigValueType};
7use flex_fuchsia_component_decl as fdecl;
8use flex_fuchsia_sys2 as fsys;
9use std::str::FromStr;
10
11use crate::cli::show::config_table_print;
12use crate::config::{UseConfigurationOrConfigField, resolve_config_decls};
13use crate::query::get_single_instance_from_query;
14
15use super::reload_cmd;
16
17pub async fn config_set_cmd<W: std::io::Write>(
18 query: String,
19 key_values: Vec<String>,
20 reload: bool,
21 lifecycle_controller: fsys::LifecycleControllerProxy,
22 realm_query: fsys::RealmQueryProxy,
23 config_override: fsys::ConfigOverrideProxy,
24 writer: W,
25) -> Result<()> {
26 let instance = get_single_instance_from_query(&query, &realm_query).await?;
27 let decls = resolve_config_decls(&realm_query, &instance.moniker).await?;
28 let fields = key_values
29 .into_iter()
30 .map(|kv| parse_config_key_value(&kv, &decls))
31 .collect::<Result<Vec<fdecl::ConfigOverride>>>()?;
32 config_override
33 .set_structured_config(instance.moniker.as_ref(), &fields)
34 .await?
35 .map_err(|e| anyhow::anyhow!("{:?}", e))?;
36 if reload {
37 reload_cmd(query, lifecycle_controller, realm_query, writer).await?;
38 }
39 Ok(())
40}
41
42pub async fn config_unset_cmd<W: std::io::Write>(
43 query: Option<String>,
44 reload: bool,
45 lifecycle_controller: fsys::LifecycleControllerProxy,
46 realm_query: fsys::RealmQueryProxy,
47 config_override: fsys::ConfigOverrideProxy,
48 writer: W,
49) -> Result<()> {
50 let (moniker, query) = match query {
51 Some(q) => {
52 let instance = get_single_instance_from_query(&q, &realm_query).await?;
53 (instance.moniker.to_string(), q)
54 }
55 None => ("".to_string(), "".to_string()),
56 };
57 config_override
58 .unset_structured_config(&moniker)
59 .await?
60 .map_err(|e| anyhow::anyhow!("{:?}", e))?;
61 if reload && !query.is_empty() {
62 reload_cmd(query, lifecycle_controller, realm_query, writer).await?;
63 }
64 Ok(())
65}
66
67pub async fn config_list_cmd<W: std::io::Write>(
68 query: String,
69 realm_query: fsys::RealmQueryProxy,
70 writer: W,
71) -> Result<()> {
72 config_table_print(query, realm_query, writer).await
73}
74
75fn parse_config_key_value(
76 kv: &str,
77 decls: &Vec<UseConfigurationOrConfigField>,
78) -> Result<fdecl::ConfigOverride> {
79 let mut kv = kv.split("=");
80 let key = kv
81 .next()
82 .ok_or_else(|| anyhow::anyhow!("invalid key=value formatting"))?
83 .trim()
84 .to_string();
85 let value = kv
86 .next()
87 .ok_or_else(|| anyhow::anyhow!("invalid key=value formatting"))?
88 .trim()
89 .to_string();
90 let config_type = decls
91 .iter()
92 .find_map(|d| match d {
93 UseConfigurationOrConfigField::UseConfiguration(use_config) => {
94 if key == use_config.target_name.to_string().to_lowercase() {
95 Some(use_config.type_.clone())
96 } else {
97 None
98 }
99 }
100 UseConfigurationOrConfigField::ConfigField(config_field) => {
101 if key == config_field.key.to_lowercase() {
102 Some(config_field.type_.clone())
103 } else {
104 None
105 }
106 }
107 })
108 .ok_or_else(|| anyhow::anyhow!("configuration capability not declared for key {key}",))?;
109 let value = parse_config_value(&value, config_type)?;
110 Ok(fdecl::ConfigOverride { key: Some(key), value: Some(value), ..Default::default() })
111}
112
113fn parse_config_value(value: &str, config_type: ConfigValueType) -> Result<fdecl::ConfigValue> {
114 let result =
115 match config_type {
116 ConfigValueType::Bool => {
117 fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(bool::from_str(value)?))
118 }
119 ConfigValueType::Uint8 => {
120 fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Uint8(u8::from_str(value)?))
121 }
122 ConfigValueType::Uint16 => {
123 fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Uint16(u16::from_str(value)?))
124 }
125 ConfigValueType::Uint32 => {
126 fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Uint32(u32::from_str(value)?))
127 }
128 ConfigValueType::Uint64 => {
129 fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Uint64(u64::from_str(value)?))
130 }
131 ConfigValueType::Int8 => {
132 fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Int8(i8::from_str(value)?))
133 }
134 ConfigValueType::Int16 => {
135 fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Int16(i16::from_str(value)?))
136 }
137 ConfigValueType::Int32 => {
138 fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Int32(i32::from_str(value)?))
139 }
140 ConfigValueType::Int64 => {
141 fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Int64(i64::from_str(value)?))
142 }
143 ConfigValueType::String { max_size: _ } => {
144 fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::String(String::from(value)))
145 }
146 ConfigValueType::Vector { nested_type, max_count: _ } => match nested_type {
147 ConfigNestedValueType::Bool => fdecl::ConfigValue::Vector(
148 fdecl::ConfigVectorValue::BoolVector(try_parse_vec::<bool>(value)?),
149 ),
150 ConfigNestedValueType::Uint8 => fdecl::ConfigValue::Vector(
151 fdecl::ConfigVectorValue::Uint8Vector(try_parse_vec::<u8>(value)?),
152 ),
153 ConfigNestedValueType::Uint16 => fdecl::ConfigValue::Vector(
154 fdecl::ConfigVectorValue::Uint16Vector(try_parse_vec::<u16>(value)?),
155 ),
156 ConfigNestedValueType::Uint32 => fdecl::ConfigValue::Vector(
157 fdecl::ConfigVectorValue::Uint32Vector(try_parse_vec::<u32>(value)?),
158 ),
159 ConfigNestedValueType::Uint64 => fdecl::ConfigValue::Vector(
160 fdecl::ConfigVectorValue::Uint64Vector(try_parse_vec::<u64>(value)?),
161 ),
162 ConfigNestedValueType::Int8 => fdecl::ConfigValue::Vector(
163 fdecl::ConfigVectorValue::Int8Vector(try_parse_vec::<i8>(value)?),
164 ),
165 ConfigNestedValueType::Int16 => fdecl::ConfigValue::Vector(
166 fdecl::ConfigVectorValue::Int16Vector(try_parse_vec::<i16>(value)?),
167 ),
168 ConfigNestedValueType::Int32 => fdecl::ConfigValue::Vector(
169 fdecl::ConfigVectorValue::Int32Vector(try_parse_vec::<i32>(value)?),
170 ),
171 ConfigNestedValueType::Int64 => fdecl::ConfigValue::Vector(
172 fdecl::ConfigVectorValue::Int64Vector(try_parse_vec::<i64>(value)?),
173 ),
174 ConfigNestedValueType::String { max_size: _ } => fdecl::ConfigValue::Vector(
175 fdecl::ConfigVectorValue::StringVector(try_parse_vec::<String>(value)?),
176 ),
177 },
178 };
179 Ok(result)
180}
181
182fn try_parse_vec<T: FromStr>(value: &str) -> Result<Vec<T>, T::Err> {
183 value.split(",").map(str::trim).map(|v| T::from_str(v)).collect()
184}
185
186#[cfg(test)]
187mod test {
188
189 use super::*;
190 use std::collections::HashMap;
191
192 use crate::test_utils::{serve_lifecycle_controller, serve_realm_query};
193 use cm_rust::FidlIntoNative;
194 use futures::TryStreamExt;
195 use moniker::Moniker;
196 use test_case::test_case;
197
198 fn serve_config_override(
199 expected_moniker: &'static str,
200 expected_kvs: Vec<cm_rust::ConfigOverride>,
201 ) -> fsys::ConfigOverrideProxy {
202 let (config_override, mut stream) =
203 fidl::endpoints::create_proxy_and_stream::<fsys::ConfigOverrideMarker>();
204
205 fuchsia_async::Task::local(async move {
206 loop {
207 match stream.try_next().await.unwrap().unwrap() {
208 fsys::ConfigOverrideRequest::SetStructuredConfig {
209 moniker,
210 fields,
211 responder,
212 } => {
213 eprintln!("SetStructuredConfig call for {moniker} with {fields:?}");
214 assert_eq!(
215 Moniker::parse_str(expected_moniker),
216 Moniker::parse_str(&moniker)
217 );
218 let result = fields
219 .into_iter()
220 .map(FidlIntoNative::fidl_into_native)
221 .map(|field| {
222 if expected_kvs.contains(&field) {
223 Ok(())
224 } else {
225 Err(fsys::ConfigOverrideError::KeyNotFound)
226 }
227 })
228 .collect::<Result<Vec<()>, fsys::ConfigOverrideError>>();
229 match result {
230 Ok(_) => responder.send(Ok(())).unwrap(),
231 Err(e) => responder.send(Err(e)).unwrap(),
232 };
233 }
234 fsys::ConfigOverrideRequest::UnsetStructuredConfig { moniker, responder } => {
235 eprintln!("UnsetStructuredConfig call for {moniker}");
236 if !moniker.is_empty() {
237 assert_eq!(
238 Moniker::parse_str(expected_moniker),
239 Moniker::parse_str(&moniker)
240 );
241 }
242 responder.send(Ok(())).unwrap();
243 }
244 fsys::ConfigOverrideRequest::_UnknownMethod { ordinal, control_handle: _, method_type, .. } => {
245 eprintln!("_UnknownMethod call with ordinal {ordinal} and method type {method_type:?}");
246 break;
247 }
248 }
249 }
250 })
251 .detach();
252 config_override
253 }
254
255 fn setup() -> (fsys::LifecycleControllerProxy, fsys::ConfigOverrideProxy, fsys::RealmQueryProxy)
256 {
257 let lifecycle_controller = serve_lifecycle_controller("./my_foo");
258 let config_override = serve_config_override(
259 "./my_foo",
260 vec![
261 cm_rust::ConfigOverride {
262 key: "foo".into(),
263 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(true)),
264 },
265 cm_rust::ConfigOverride {
266 key: "bar".into(),
267 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint64(42)),
268 },
269 ],
270 );
271 let instances = vec![fsys::Instance {
272 moniker: Some("./my_foo".to_string()),
273 url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
274 ..Default::default()
275 }];
276 let manifests = HashMap::from([(
277 "./my_foo".to_string(),
278 fdecl::Component {
279 uses: Some(vec![fdecl::Use::Config(fdecl::UseConfiguration {
280 source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
281 source_name: Some("fuchsia.foo".to_string()),
282 target_name: Some("foo".to_string()),
283 type_: Some(fdecl::ConfigType {
284 layout: fdecl::ConfigTypeLayout::Bool,
285 constraints: Vec::new(),
286 parameters: None,
287 }),
288 ..Default::default()
289 })]),
290 config: Some(fdecl::ConfigSchema {
291 fields: Some(vec![fdecl::ConfigField {
292 key: Some("bar".to_string()),
293 type_: Some(fdecl::ConfigType {
294 layout: fdecl::ConfigTypeLayout::Uint64,
295 constraints: Vec::new(),
296 parameters: None,
297 }),
298 ..Default::default()
299 }]),
300 checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
301 value_source: Some(fdecl::ConfigValueSource::PackagePath("".to_string())),
302 ..Default::default()
303 }),
304 ..Default::default()
305 },
306 )]);
307 let configs = HashMap::from([(
308 "./my_foo".to_string(),
309 fdecl::ResolvedConfig {
310 fields: vec![fdecl::ResolvedConfigField {
311 key: "foo".to_string(),
312 value: fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(false)),
313 }],
314 checksum: fdecl::ConfigChecksum::Sha256([0; 32]),
315 },
316 )]);
317 let realm_query = serve_realm_query(instances, manifests, configs, HashMap::new());
318 (lifecycle_controller, config_override, realm_query)
319 }
320
321 #[test_case(vec!["foo=true".to_string()], false, true; "no reload succeeds")]
322 #[test_case(vec!["foo=true".to_string()], true, true; "reload succeeds")]
323 #[test_case(vec!["foo=42".to_string()], false, false; "wrong type fails")]
324 #[test_case(vec!["bar=42".to_string()], false, true; "structured config override succeeds")]
325 #[fuchsia_async::run_singlethreaded(test)]
326 async fn config_set(key_values: Vec<String>, reload: bool, succeeds: bool) {
327 let (lifecycle_controller, config_override, realm_query) = setup();
328 let writer = Vec::new();
329 assert_eq!(
330 config_set_cmd(
331 "my_foo".to_string(),
332 key_values,
333 reload,
334 lifecycle_controller,
335 realm_query,
336 config_override,
337 writer,
338 )
339 .await
340 .is_ok(),
341 succeeds
342 );
343 }
344
345 #[test_case(Some("my_foo".to_string()), false, true; "no reload succeeds")]
346 #[test_case(Some("my_foo".to_string()), true, true; "reload succeeds")]
347 #[test_case(Some("my_bar".to_string()), false, false; "unknown query fails")]
348 #[test_case(None, false, true; "empty moniker succeeds")]
349 #[fuchsia_async::run_singlethreaded(test)]
350 async fn config_unset(query: Option<String>, reload: bool, succeeds: bool) {
351 let (lifecycle_controller, config_override, realm_query) = setup();
352 let writer = Vec::new();
353 assert_eq!(
354 config_unset_cmd(
355 query,
356 reload,
357 lifecycle_controller,
358 realm_query,
359 config_override,
360 writer,
361 )
362 .await
363 .is_ok(),
364 succeeds
365 );
366 }
367
368 #[test_case("true", ConfigValueType::Bool, fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(true)); "single bool")]
369 #[test_case("42", ConfigValueType::Uint8, fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Uint8(42)); "single u8")]
370 #[test_case("42", ConfigValueType::Uint16, fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Uint16(42)); "single u16")]
371 #[test_case("42", ConfigValueType::Uint32, fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Uint32(42)); "single u32")]
372 #[test_case("42", ConfigValueType::Uint64, fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Uint64(42)); "single u64")]
373 #[test_case("42", ConfigValueType::Int8, fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Int8(42)); "single i8")]
374 #[test_case("42", ConfigValueType::Int16, fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Int16(42)); "single i16")]
375 #[test_case("42", ConfigValueType::Int32, fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Int32(42)); "single i32")]
376 #[test_case("42", ConfigValueType::Int64, fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Int64(42)); "single i64")]
377 #[test_case("a string", ConfigValueType::String { max_size: 1024}, fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::String(String::from("a string"))); "single String")]
378 #[test_case("true, false", ConfigValueType::Vector { nested_type: ConfigNestedValueType::Bool, max_count: 32 }, fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::BoolVector(vec![true, false])); "bool vector")]
379 #[test_case("42, 24", ConfigValueType::Vector { nested_type: ConfigNestedValueType::Uint8, max_count: 32 }, fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::Uint8Vector(vec![42, 24])); "u8 vector")]
380 #[test_case("42, 24", ConfigValueType::Vector { nested_type: ConfigNestedValueType::Uint16, max_count: 32 }, fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::Uint16Vector(vec![42, 24])); "u16 vector")]
381 #[test_case("42, 24", ConfigValueType::Vector { nested_type: ConfigNestedValueType::Uint32, max_count: 32 }, fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::Uint32Vector(vec![42, 24])); "u32 vector")]
382 #[test_case("42, 24", ConfigValueType::Vector { nested_type: ConfigNestedValueType::Uint64, max_count: 32 }, fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::Uint64Vector(vec![42, 24])); "u64 vector")]
383 #[test_case("42, 24", ConfigValueType::Vector { nested_type: ConfigNestedValueType::Int8, max_count: 32 }, fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::Int8Vector(vec![42, 24])); "i8 vector")]
384 #[test_case("42, 24", ConfigValueType::Vector { nested_type: ConfigNestedValueType::Int16, max_count: 32 }, fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::Int16Vector(vec![42, 24])); "i16 vector")]
385 #[test_case("42, 24", ConfigValueType::Vector { nested_type: ConfigNestedValueType::Int32, max_count: 32 }, fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::Int32Vector(vec![42, 24])); "i32 vector")]
386 #[test_case("42, 24", ConfigValueType::Vector { nested_type: ConfigNestedValueType::Int64, max_count: 32 }, fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::Int64Vector(vec![42, 24])); "i64 vector")]
387 #[test_case("person, camera, tv", ConfigValueType::Vector { nested_type: ConfigNestedValueType::String { max_size: 1024 }, max_count: 32 }, fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::StringVector(vec![String::from("person"), String::from("camera"), String::from("tv")])); "string vector")]
388 #[fuchsia::test]
389 fn test_parse_config_value(
390 value: &str,
391 config_type: ConfigValueType,
392 expected: fdecl::ConfigValue,
393 ) {
394 let cv = parse_config_value(value, config_type).unwrap();
395 assert_eq!(cv, expected);
396 }
397}