blob: 565293f672c17e7e46ad7010de2145ebfa7efbfc [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use fuchsia_url::pkg_url::PkgUrl;
use log::{error, warn};
use omaha_client::installer::Plan;
use omaha_client::{
protocol::{
request::InstallSource,
response::{OmahaStatus, Response},
},
request_builder::RequestParams,
};
use thiserror::Error;
#[derive(Debug, PartialEq)]
pub struct FuchsiaInstallPlan {
/// The fuchsia TUF repo URL, e.g. fuchsia-pkg://fuchsia.com/update/0?hash=...
pub url: PkgUrl,
pub install_source: InstallSource,
}
#[derive(Debug, Error, PartialEq)]
pub enum InstallPlanErrors {
#[error("Fuchsia Install Plan could not be created from response")]
Failed,
}
impl Plan for FuchsiaInstallPlan {
type Error = InstallPlanErrors;
fn try_create_from(
request_params: &RequestParams,
resp: &Response,
) -> Result<Self, Self::Error> {
let (app, rest) = if let Some((app, rest)) = resp.apps.split_first() {
(app, rest)
} else {
error!("No app in Omaha response.");
return Err(InstallPlanErrors::Failed);
};
if !rest.is_empty() {
warn!("Only 1 app is supported, found {}", resp.apps.len());
}
if app.status != OmahaStatus::Ok {
error!("Found non-ok app status: {:?}", app.status);
return Err(InstallPlanErrors::Failed);
}
let update_check = if let Some(update_check) = &app.update_check {
update_check
} else {
error!("No update_check in Omaha response.");
return Err(InstallPlanErrors::Failed);
};
let urls = match update_check.status {
OmahaStatus::Ok => {
if let Some(urls) = &update_check.urls {
&urls.url
} else {
error!("No urls in Omaha response.");
return Err(InstallPlanErrors::Failed);
}
}
OmahaStatus::NoUpdate => {
error!("Was asked to create an install plan for a NoUpdate Omaha response");
return Err(InstallPlanErrors::Failed);
}
_ => {
warn!("Unexpected update check status: {:?}", update_check.status);
if let Some(info) = &update_check.info {
warn!("update check status info: {}", info);
}
return Err(InstallPlanErrors::Failed);
}
};
let (url, rest) = if let Some((url, rest)) = urls.split_first() {
(url, rest)
} else {
error!("No url in Omaha response.");
return Err(InstallPlanErrors::Failed);
};
if !rest.is_empty() {
warn!("Only 1 url is supported, found {}", urls.len());
}
let manifest = if let Some(manifest) = &update_check.manifest {
manifest
} else {
error!("No manifest in Omaha response.");
return Err(InstallPlanErrors::Failed);
};
let (package, rest) = if let Some((package, rest)) = manifest.packages.package.split_first()
{
(package, rest)
} else {
error!("No package in Omaha response.");
return Err(InstallPlanErrors::Failed);
};
if !rest.is_empty() {
warn!("Only 1 package is supported, found {}", manifest.packages.package.len());
}
let full_url = url.codebase.clone() + &package.name;
match PkgUrl::parse(&full_url) {
Ok(url) => {
Ok(FuchsiaInstallPlan { url, install_source: request_params.source.clone() })
}
Err(err) => {
error!("Failed to parse {} to PkgUrl: {}", url.codebase, err);
Err(InstallPlanErrors::Failed)
}
}
}
fn id(&self) -> String {
self.url.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use omaha_client::protocol::response::{App, Manifest, Package, Packages, UpdateCheck};
const TEST_URL_BASE: &str = "fuchsia-pkg://fuchsia.com/";
const TEST_PACKAGE_NAME: &str = "update/0";
#[test]
fn test_simple_response() {
let request_params = RequestParams::default();
let mut update_check = UpdateCheck::ok(vec![TEST_URL_BASE.to_string()]);
update_check.manifest = Some(Manifest {
packages: Packages {
package: vec![Package {
name: TEST_PACKAGE_NAME.to_string(),
..Package::default()
}],
},
..Manifest::default()
});
let response = Response {
apps: vec![App { update_check: Some(update_check), ..App::default() }],
..Response::default()
};
let install_plan = FuchsiaInstallPlan::try_create_from(&request_params, &response).unwrap();
assert_eq!(install_plan.url.to_string(), TEST_URL_BASE.to_string() + TEST_PACKAGE_NAME);
assert_eq!(install_plan.install_source, request_params.source);
}
#[test]
fn test_no_app() {
let request_params = RequestParams::default();
let response = Response::default();
assert_eq!(
FuchsiaInstallPlan::try_create_from(&request_params, &response),
Err(InstallPlanErrors::Failed)
);
}
#[test]
fn test_multiple_app() {
let request_params = RequestParams::default();
let mut update_check = UpdateCheck::ok(vec![TEST_URL_BASE.to_string()]);
update_check.manifest = Some(Manifest {
packages: Packages {
package: vec![Package {
name: TEST_PACKAGE_NAME.to_string(),
..Package::default()
}],
},
..Manifest::default()
});
let response = Response {
apps: vec![App { update_check: Some(update_check), ..App::default() }],
..Response::default()
};
let install_plan = FuchsiaInstallPlan::try_create_from(&request_params, &response).unwrap();
assert_eq!(install_plan.url.to_string(), TEST_URL_BASE.to_string() + TEST_PACKAGE_NAME);
assert_eq!(install_plan.install_source, request_params.source);
}
#[test]
fn test_no_update_check() {
let request_params = RequestParams::default();
let response = Response { apps: vec![App::default()], ..Response::default() };
assert_eq!(
FuchsiaInstallPlan::try_create_from(&request_params, &response),
Err(InstallPlanErrors::Failed)
);
}
#[test]
fn test_no_urls() {
let request_params = RequestParams::default();
let response = Response {
apps: vec![App { update_check: Some(UpdateCheck::default()), ..App::default() }],
..Response::default()
};
assert_eq!(
FuchsiaInstallPlan::try_create_from(&request_params, &response),
Err(InstallPlanErrors::Failed)
);
}
#[test]
fn test_app_error_status() {
let request_params = RequestParams::default();
let response = Response {
apps: vec![App {
status: OmahaStatus::Error("error-unknownApplication".to_string()),
..App::default()
}],
..Response::default()
};
assert_eq!(
FuchsiaInstallPlan::try_create_from(&request_params, &response),
Err(InstallPlanErrors::Failed)
);
}
#[test]
fn test_no_update() {
let request_params = RequestParams::default();
let response = Response {
apps: vec![App { update_check: Some(UpdateCheck::no_update()), ..App::default() }],
..Response::default()
};
assert_eq!(
FuchsiaInstallPlan::try_create_from(&request_params, &response),
Err(InstallPlanErrors::Failed)
);
}
#[test]
fn test_invalid_url() {
let request_params = RequestParams::default();
let response = Response {
apps: vec![App {
update_check: Some(UpdateCheck::ok(vec!["invalid-url".to_string()])),
..App::default()
}],
..Response::default()
};
assert_eq!(
FuchsiaInstallPlan::try_create_from(&request_params, &response),
Err(InstallPlanErrors::Failed)
);
}
#[test]
fn test_no_manifest() {
let request_params = RequestParams::default();
let response = Response {
apps: vec![App {
update_check: Some(UpdateCheck::ok(vec![TEST_URL_BASE.to_string()])),
..App::default()
}],
..Response::default()
};
assert_eq!(
FuchsiaInstallPlan::try_create_from(&request_params, &response),
Err(InstallPlanErrors::Failed)
);
}
#[test]
fn test_install_plan_id() {
let url = TEST_URL_BASE.to_string() + TEST_PACKAGE_NAME;
let install_plan = FuchsiaInstallPlan {
url: PkgUrl::parse(&url).unwrap(),
install_source: InstallSource::ScheduledTask,
};
assert_eq!(install_plan.id(), url);
}
}