Skip to main content

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 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}