| // Copyright 2020 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 { |
| crate::cache::Cache, |
| anyhow::{Context, Error}, |
| fidl_fuchsia_boot::{ArgumentsRequest, ArgumentsRequestStream}, |
| fidl_fuchsia_pkg::PackageCacheMarker, |
| fuchsia_async as fasync, |
| fuchsia_component::{ |
| client::{App, AppBuilder}, |
| server::{NestedEnvironment, ServiceFs, ServiceObj}, |
| }, |
| fuchsia_syslog::fx_log_err, |
| fuchsia_zircon::{self as zx}, |
| futures::prelude::*, |
| std::{io::Write, sync::Arc}, |
| }; |
| |
| const RESOLVER_URL: &str = |
| "fuchsia-pkg://fuchsia.com/isolated-swd-components#meta/pkg-resolver-isolated.cmx"; |
| const SSL_CERTS_PATH: &str = "/config/ssl"; |
| |
| /// A simple implementation of the fuchsia.boot.Arguments protocol, |
| /// which reports the selected TUF channel to its caller. |
| struct IsolatedBootArgs { |
| channel: Option<String>, |
| } |
| |
| impl IsolatedBootArgs { |
| pub fn new(channel: Option<String>) -> Self { |
| IsolatedBootArgs { channel } |
| } |
| |
| pub async fn serve(self: Arc<Self>, mut stream: ArgumentsRequestStream) { |
| while let Some(req) = stream.try_next().await.unwrap() { |
| match req { |
| ArgumentsRequest::GetString { key, responder } => { |
| if key == "tuf_repo_config" { |
| responder.send(self.channel.as_ref().map(|c| c.as_str())).unwrap(); |
| } else { |
| fx_log_err!("Unexpected arguments GetString: {}, closing channel.", key); |
| } |
| } |
| _ => fx_log_err!("Unexpected arguments request, closing channel."), |
| } |
| } |
| } |
| } |
| |
| /// Represents the sandboxed package resolver. |
| pub struct Resolver { |
| _pkg_resolver: App, |
| pkg_resolver_directory: Arc<zx::Channel>, |
| _env: NestedEnvironment, |
| } |
| |
| impl Resolver { |
| /// Launch the package resolver using the given components, TUF repo/channel config, |
| /// and SSL certificate folder. |
| pub fn launch( |
| cache: Arc<Cache>, |
| repo_config: std::fs::File, |
| channel: &str, |
| ssl_dir: std::fs::File, |
| ) -> Result<Self, Error> { |
| Resolver::launch_with_components( |
| cache, |
| repo_config, |
| Some(channel.to_owned()), |
| ssl_dir, |
| RESOLVER_URL, |
| ) |
| } |
| |
| /// Launch the package resolver. This is the same as `launch`, but the URL for the |
| /// resolver's manifest must be provided. |
| fn launch_with_components( |
| cache: Arc<Cache>, |
| repo_config: std::fs::File, |
| channel: Option<String>, |
| ssl_dir: std::fs::File, |
| resolver_url: &str, |
| ) -> Result<Self, Error> { |
| let mut pkg_resolver = AppBuilder::new(resolver_url) |
| .add_dir_to_namespace("/config/data/repositories".to_owned(), repo_config)? |
| .add_dir_to_namespace(SSL_CERTS_PATH.to_owned(), ssl_dir)?; |
| |
| let boot_args = Arc::new(IsolatedBootArgs::new(channel)); |
| |
| let mut fs: ServiceFs<ServiceObj<'_, ()>> = ServiceFs::new(); |
| fs.add_proxy_service::<fidl_fuchsia_net_name::LookupMarker, _>() |
| .add_proxy_service::<fidl_fuchsia_posix_socket::ProviderMarker, _>() |
| .add_proxy_service::<fidl_fuchsia_logger::LogSinkMarker, _>() |
| .add_proxy_service::<fidl_fuchsia_tracing_provider::RegistryMarker, _>() |
| .add_proxy_service_to::<PackageCacheMarker, _>(cache.directory_request()); |
| |
| fs.add_fidl_service(move |stream: ArgumentsRequestStream| { |
| fasync::Task::spawn(Arc::clone(&boot_args).serve(stream)).detach() |
| }); |
| |
| // We use a salt so the unit tests work as expected. |
| let env = fs.create_salted_nested_environment("isolated-swd-env")?; |
| fasync::Task::spawn(fs.collect()).detach(); |
| |
| let directory = |
| pkg_resolver.directory_request().context("getting directory request")?.clone(); |
| let pkg_resolver = |
| pkg_resolver.spawn(env.launcher()).context("launching package resolver")?; |
| |
| Ok(Resolver { _pkg_resolver: pkg_resolver, pkg_resolver_directory: directory, _env: env }) |
| } |
| |
| pub fn directory_request(&self) -> Arc<fuchsia_zircon::Channel> { |
| self.pkg_resolver_directory.clone() |
| } |
| } |
| |
| pub mod for_tests { |
| use { |
| super::*, |
| crate::cache::for_tests::CacheForTest, |
| anyhow::anyhow, |
| fidl_fuchsia_io as fio, |
| fidl_fuchsia_pkg::PackageResolverMarker, |
| fidl_fuchsia_pkg_ext::RepositoryConfigs, |
| fuchsia_pkg_testing::{serve::ServedRepository, Repository}, |
| fuchsia_url::RepositoryUrl, |
| }; |
| |
| const SSL_TEST_CERTS_PATH: &str = "/pkg/data/ssl"; |
| pub const EMPTY_REPO_PATH: &str = "/pkg/empty-repo"; |
| |
| /// This wraps the `Resolver` in order to reduce test boilerplate. |
| pub struct ResolverForTest { |
| pub cache: CacheForTest, |
| pub resolver: Arc<Resolver>, |
| _served_repo: ServedRepository, |
| } |
| |
| impl ResolverForTest { |
| #[cfg(test)] |
| pub async fn new( |
| repo: Arc<Repository>, |
| repo_url: RepositoryUrl, |
| channel: Option<String>, |
| ) -> Result<Self, Error> { |
| Self::new_with_component( |
| repo, |
| repo_url, |
| channel, |
| "fuchsia-pkg://fuchsia.com/isolated-swd-tests#meta/pkg-resolver-isolated.cmx", |
| "fuchsia-pkg://fuchsia.com/isolated-swd-tests#meta/pkg-cache-isolated.cmx", |
| ) |
| .await |
| } |
| |
| /// Create a resolver, which will resolve packages using the given Repository. |
| /// This will also create the components used by the resolver. |
| /// SSL certificates for use by the served repository should be located at /pkg/data/ssl, |
| /// and the `pkgsvr` binary should be at /pkg/bin/pkgsvr. |
| /// |
| /// Arguments: |
| /// * `repo`: `Repository` object, which will be used as the repo to fetch packages from. |
| /// * `repo_url`: Base URL of the repository, e.g. `fuchsia-pkg://my-package-repository.com` |
| /// if you want your packages to be resolved with URLs like |
| /// `fuchsia-pkg://my-package-repository.com/package-name`. |
| /// * `channel`: Update channel that the resolver should use. |
| /// * `resolver_url`: fuchsia-pkg:// URL to use when launching the package resolver. |
| /// * `cache_url`: fuchsia-pkg:// URL to use when launching the package cache. |
| pub async fn new_with_component( |
| repo: Arc<Repository>, |
| repo_url: RepositoryUrl, |
| channel: Option<String>, |
| resolver_url: &str, |
| cache_url: &str, |
| ) -> Result<Self, Error> { |
| let cache = CacheForTest::new_with_component(cache_url).context("launching cache")?; |
| |
| // Set up the repository config for pkg-resolver. |
| let served_repo = Arc::clone(&repo).server().start()?; |
| |
| let repo_config = |
| RepositoryConfigs::Version1(vec![served_repo.make_repo_config(repo_url)]); |
| let tempdir = tempfile::tempdir()?; |
| let mut temp_path = tempdir.path().to_owned(); |
| temp_path.push("test.json"); |
| let path = temp_path.as_path(); |
| let mut file = |
| std::io::BufWriter::new(std::fs::File::create(path).context("creating file")?); |
| serde_json::to_writer(&mut file, &repo_config).unwrap(); |
| file.flush().unwrap(); |
| |
| // Launch the resolver. |
| let repo_dir = |
| std::fs::File::open(tempdir.into_path()).context("opening repo tmpdir")?; |
| let ssl_certs = |
| std::fs::File::open(SSL_TEST_CERTS_PATH).context("opening ssl certificates dir")?; |
| let resolver = Resolver::launch_with_components( |
| Arc::clone(&cache.cache), |
| repo_dir, |
| channel, |
| ssl_certs, |
| resolver_url, |
| ) |
| .context("launching resolver")?; |
| |
| Ok(ResolverForTest { cache, resolver: Arc::new(resolver), _served_repo: served_repo }) |
| } |
| |
| /// Resolve a package using the resolver, returning the root directory of the package. |
| pub async fn resolve_package(&self, url: &str) -> Result<fio::DirectoryProxy, Error> { |
| let resolver = self |
| .resolver |
| ._pkg_resolver |
| .connect_to_protocol::<PackageResolverMarker>() |
| .context("getting resolver")?; |
| let (package, package_remote) = |
| fidl::endpoints::create_proxy().context("creating package directory endpoints")?; |
| let () = resolver |
| .resolve(url, package_remote) |
| .await |
| .unwrap() |
| .map_err(|e| anyhow!("Package resolver error: {:?}", e))?; |
| Ok(package) |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| pub mod tests { |
| use { |
| super::for_tests::{ResolverForTest, EMPTY_REPO_PATH}, |
| super::*, |
| fuchsia_pkg_testing::{PackageBuilder, RepositoryBuilder}, |
| }; |
| |
| const TEST_REPO_URL: &str = "fuchsia-pkg://test"; |
| |
| #[fasync::run_singlethreaded(test)] |
| pub async fn test_resolver() -> Result<(), Error> { |
| let name = "test-resolver"; |
| let package = PackageBuilder::new(name) |
| .add_resource_at("data/file1", "hello".as_bytes()) |
| .add_resource_at("data/file2", "hello two".as_bytes()) |
| .build() |
| .await |
| .unwrap(); |
| let repo = Arc::new( |
| RepositoryBuilder::from_template_dir(EMPTY_REPO_PATH) |
| .add_package(&package) |
| .build() |
| .await |
| .context("Building repo") |
| .unwrap(), |
| ); |
| |
| let resolver = ResolverForTest::new(repo, TEST_REPO_URL.parse().unwrap(), None) |
| .await |
| .context("launching resolver")?; |
| let root_dir = |
| resolver.resolve_package(&format!("{}/{}", TEST_REPO_URL, name)).await.unwrap(); |
| |
| package.verify_contents(&root_dir).await.unwrap(); |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| pub async fn test_resolver_with_channel() -> Result<(), Error> { |
| let name = "test-resolver-channel"; |
| let package = PackageBuilder::new(name) |
| .add_resource_at("data/file1", "hello".as_bytes()) |
| .add_resource_at("data/file2", "hello two".as_bytes()) |
| .build() |
| .await |
| .unwrap(); |
| let repo = Arc::new( |
| RepositoryBuilder::from_template_dir(EMPTY_REPO_PATH) |
| .add_package(&package) |
| .build() |
| .await |
| .context("Building repo") |
| .unwrap(), |
| ); |
| |
| let resolver = ResolverForTest::new( |
| repo, |
| "fuchsia-pkg://x64.resolver-test-channel.fuchsia.com".parse().unwrap(), |
| Some("resolver-test-channel".to_owned()), |
| ) |
| .await |
| .context("launching resolver")?; |
| let root_dir = |
| resolver.resolve_package(&format!("fuchsia-pkg://fuchsia.com/{}", name)).await.unwrap(); |
| |
| package.verify_contents(&root_dir).await.unwrap(); |
| Ok(()) |
| } |
| } |