eager_package_config/
omaha_client.rs

1// Copyright 2022 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::{Context as _, Error};
6use channel_config::ChannelConfigs;
7use fuchsia_url::UnpinnedAbsolutePackageUrl;
8use omaha_client::cup_ecdsa::PublicKeys;
9use serde::{Deserialize, Serialize};
10use std::io;
11
12const EAGER_PACKAGE_CONFIG_PATH: &str = "/config/data/eager_package_config.json";
13
14#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
15pub struct OmahaServer {
16    pub service_url: String,
17    pub public_keys: PublicKeys,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
21pub struct EagerPackageConfig {
22    pub url: UnpinnedAbsolutePackageUrl,
23    pub flavor: Option<String>,
24    pub channel_config: ChannelConfigs,
25}
26
27#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
28pub struct EagerPackageConfigs {
29    pub server: OmahaServer,
30    pub packages: Vec<EagerPackageConfig>,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
34pub struct EagerPackageConfigsJson {
35    pub eager_package_configs: Vec<EagerPackageConfigs>,
36}
37
38impl EagerPackageConfigs {
39    /// Load the eager package config from namespace.
40    pub fn from_namespace() -> Result<Self, Error> {
41        let file = std::fs::File::open(EAGER_PACKAGE_CONFIG_PATH)
42            .context("opening eager package config file")?;
43        Self::from_reader(io::BufReader::new(file))
44    }
45
46    fn from_reader(reader: impl io::Read) -> Result<Self, Error> {
47        let eager_package_configs_json: EagerPackageConfigsJson =
48            serde_json::from_reader(reader).context("parsing eager package config")?;
49        // Omaha client only supports one Omaha server at a time; just take the
50        // first server config in this file.
51        if eager_package_configs_json.eager_package_configs.len() > 1 {
52            log::error!(
53                "Warning: this eager package config JSON file contained more \
54                than one Omaha server config, but omaha-client only supports \
55                one Omaha server."
56            );
57        }
58        let eager_package_configs =
59            eager_package_configs_json.eager_package_configs.into_iter().next().ok_or_else(
60                || {
61                    anyhow::anyhow!(
62                        "Eager package config JSON did not contain any server-and-package configs."
63                    )
64                },
65            )?;
66        for package in &eager_package_configs.packages {
67            package.channel_config.validate().context("validating eager package channel config")?;
68        }
69
70        Ok(eager_package_configs)
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use assert_matches::assert_matches;
78    use channel_config::ChannelConfig;
79    use omaha_client::cup_ecdsa::test_support::{
80        make_default_json_public_keys_for_test, make_default_public_key_for_test,
81        make_default_public_key_id_for_test,
82    };
83    use omaha_client::cup_ecdsa::PublicKeyAndId;
84    use pretty_assertions::assert_eq;
85
86    #[test]
87    fn parse_eager_package_configs_json() {
88        let json = serde_json::json!(
89        {
90            "eager_package_configs": [
91                {
92                    "server": {
93                        "service_url": "https://example.com",
94                        "public_keys": make_default_json_public_keys_for_test(),
95                    },
96                    "packages":
97                    [
98                        {
99                            "url": "fuchsia-pkg://example.com/package",
100                            "flavor": "debug",
101                            "channel_config":
102                                {
103                                    "channels":
104                                        [
105                                            {
106                                                "name": "stable",
107                                                "repo": "stable",
108                                                "appid": "1a2b3c4d"
109                                            },
110                                            {
111                                                "name": "beta",
112                                                "repo": "beta",
113                                                "appid": "1a2b3c4d"
114                                            },
115                                            {
116                                                "name": "alpha",
117                                                "repo": "alpha",
118                                                "appid": "1a2b3c4d"
119                                            },
120                                            {
121                                                "name": "test",
122                                                "repo": "test",
123                                                "appid": "2b3c4d5e"
124                                            }
125                                        ],
126                                    "default_channel": "stable"
127                                }
128                        },
129                        {
130                            "url": "fuchsia-pkg://example.com/package2",
131                            "channel_config":
132                                {
133                                    "channels":
134                                        [
135                                            {
136                                                "name": "stable",
137                                                "repo": "stable",
138                                                "appid": "3c4d5e6f"
139                                            }
140                                        ]
141                                }
142                        }
143                    ]
144                }
145            ]
146        });
147
148        assert_eq!(
149            EagerPackageConfigs::from_reader(json.to_string().as_bytes()).unwrap(),
150            EagerPackageConfigs {
151                server: OmahaServer {
152                    service_url: "https://example.com".into(),
153                    public_keys: PublicKeys {
154                        latest: PublicKeyAndId {
155                            id: make_default_public_key_id_for_test(),
156                            key: make_default_public_key_for_test(),
157                        },
158                        historical: vec![],
159                    }
160                },
161                packages: vec![
162                    EagerPackageConfig {
163                        url: UnpinnedAbsolutePackageUrl::parse("fuchsia-pkg://example.com/package")
164                            .unwrap(),
165                        flavor: Some("debug".into()),
166                        channel_config: ChannelConfigs {
167                            default_channel: Some("stable".into()),
168                            known_channels: vec![
169                                ChannelConfig {
170                                    name: "stable".into(),
171                                    repo: "stable".into(),
172                                    appid: Some("1a2b3c4d".into()),
173                                    check_interval_secs: None,
174                                },
175                                ChannelConfig {
176                                    name: "beta".into(),
177                                    repo: "beta".into(),
178                                    appid: Some("1a2b3c4d".into()),
179                                    check_interval_secs: None,
180                                },
181                                ChannelConfig {
182                                    name: "alpha".into(),
183                                    repo: "alpha".into(),
184                                    appid: Some("1a2b3c4d".into()),
185                                    check_interval_secs: None,
186                                },
187                                ChannelConfig {
188                                    name: "test".into(),
189                                    repo: "test".into(),
190                                    appid: Some("2b3c4d5e".into()),
191                                    check_interval_secs: None,
192                                },
193                            ]
194                        },
195                    },
196                    EagerPackageConfig {
197                        url: UnpinnedAbsolutePackageUrl::parse(
198                            "fuchsia-pkg://example.com/package2"
199                        )
200                        .unwrap(),
201                        flavor: None,
202                        channel_config: ChannelConfigs {
203                            default_channel: None,
204                            known_channels: vec![ChannelConfig {
205                                name: "stable".into(),
206                                repo: "stable".into(),
207                                appid: Some("3c4d5e6f".into()),
208                                check_interval_secs: None,
209                            },]
210                        },
211                    },
212                ]
213            }
214        );
215    }
216
217    #[test]
218    fn parse_eager_package_configs_json_reject_invalid() {
219        let json = br#"
220        {
221            "eager_package_configs": [
222                {
223                    "server": {},
224                    "packages":
225                    [
226                        {
227                            "url": "fuchsia-pkg://example.com/package",
228                            "channel_config":
229                                {
230                                    "channels":
231                                        [
232                                            {
233                                                "name": "stable",
234                                                "repo": "stable",
235                                            }
236                                        ],
237                                    "default_channel": "invalid"
238                                }
239                        }
240                    ]
241                }
242            ]
243        }"#;
244        assert_matches!(EagerPackageConfigs::from_reader(&json[..]), Err(_));
245    }
246}