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