component_debug/cli/
config.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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}