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