[pkgctl] Fetch and process repository configs via http.
pkgctl is now able to fetch repository configs via providing a http URL.
To allow for compatibility / transition during amberctl's depreciation,
pkgctl now also processes v1 and v2 conig.json structures.
This implementation leverages the FIDL http service.
Fixed: 71468
Change-Id: Id47472e9ba5c4d2ac34feeb4af0408cabd0c284b
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/495859
Reviewed-by: Erick Tryzelaar <etryzelaar@google.com>
Reviewed-by: Amit Uttamchandani <amituttam@google.com>
Commit-Queue: Thomas Zander <thomasalpinus@google.com>
diff --git a/src/sys/pkg/bin/pkgctl/BUILD.gn b/src/sys/pkg/bin/pkgctl/BUILD.gn
index 152a7bb..9ffcd04 100644
--- a/src/sys/pkg/bin/pkgctl/BUILD.gn
+++ b/src/sys/pkg/bin/pkgctl/BUILD.gn
@@ -16,19 +16,26 @@
deps = [
"//garnet/lib/rust/files_async",
"//sdk/fidl/fuchsia.io:fuchsia.io-rustc",
+ "//sdk/fidl/fuchsia.net:fuchsia.net-rustc",
+ "//sdk/fidl/fuchsia.net.http:fuchsia.net.http-rustc",
+ "//sdk/fidl/fuchsia.net.stack:fuchsia.net.stack-rustc",
"//sdk/fidl/fuchsia.pkg:fuchsia.pkg-rustc",
"//sdk/fidl/fuchsia.pkg.rewrite:fuchsia.pkg.rewrite-rustc",
"//sdk/fidl/fuchsia.space:fuchsia.space-rustc",
"//src/lib/fidl/rust/fidl",
"//src/lib/fuchsia-async",
"//src/lib/fuchsia-component",
+ "//src/lib/fuchsia-url",
"//src/lib/zircon/rust:fuchsia-zircon",
"//src/sys/lib/fidl-fuchsia-pkg-ext",
"//src/sys/lib/fidl-fuchsia-pkg-rewrite-ext",
"//third_party/rust_crates:anyhow",
"//third_party/rust_crates:argh",
"//third_party/rust_crates:futures",
+ "//third_party/rust_crates:hex",
"//third_party/rust_crates:matches",
+ "//third_party/rust_crates:regex",
+ "//third_party/rust_crates:serde",
"//third_party/rust_crates:serde_json",
"//third_party/rust_crates:thiserror",
]
@@ -38,6 +45,7 @@
"src/args.rs",
"src/error.rs",
"src/main.rs",
+ "src/v1repoconf.rs",
]
visibility = [
diff --git a/src/sys/pkg/bin/pkgctl/meta/pkgctl.cmx b/src/sys/pkg/bin/pkgctl/meta/pkgctl.cmx
index 45c790b..86738bd 100644
--- a/src/sys/pkg/bin/pkgctl/meta/pkgctl.cmx
+++ b/src/sys/pkg/bin/pkgctl/meta/pkgctl.cmx
@@ -7,6 +7,7 @@
},
"sandbox": {
"services": [
+ "fuchsia.net.http.Loader",
"fuchsia.pkg.PackageCache",
"fuchsia.pkg.PackageResolver",
"fuchsia.pkg.PackageResolverAdmin",
diff --git a/src/sys/pkg/bin/pkgctl/src/args.rs b/src/sys/pkg/bin/pkgctl/src/args.rs
index 97813fd..9a0b888 100644
--- a/src/sys/pkg/bin/pkgctl/src/args.rs
+++ b/src/sys/pkg/bin/pkgctl/src/args.rs
@@ -83,12 +83,64 @@
#[argh(subcommand, name = "add")]
/// Add a source repository.
pub struct RepoAddCommand {
- /// path to a respository config file, in JSON format, which contains the different repository metadata and URLs.
- #[argh(option, short = 'f')]
+ #[argh(subcommand)]
+ pub subcommand: RepoAddSubCommand,
+}
+
+#[derive(FromArgs, Debug, PartialEq)]
+#[argh(subcommand)]
+pub enum RepoAddSubCommand {
+ File(RepoAddFileCommand),
+ Url(RepoAddUrlCommand),
+}
+
+#[derive(Debug, PartialEq)]
+pub enum RepoConfigFormat {
+ Version1,
+ Version2,
+}
+
+#[derive(FromArgs, Debug, PartialEq)]
+#[argh(subcommand, name = "file")]
+/// Add a respository config from a local file, in JSON format, which contains the different repository metadata and URLs.
+pub struct RepoAddFileCommand {
+ /// the expected config.json file format version.
+ #[argh(
+ option,
+ short = 'f',
+ default = "RepoConfigFormat::Version2",
+ from_str_fn(repo_config_format)
+ )]
+ pub format: RepoConfigFormat,
+ /// name of the source (a name from the URL will be derived if not provided).
+ #[argh(option, short = 'n')]
+ pub name: Option<String>,
+ /// respository config file, in JSON format, which contains the different repository metadata and URLs.
+ #[argh(positional)]
pub file: PathBuf,
}
#[derive(FromArgs, Debug, PartialEq)]
+#[argh(subcommand, name = "url")]
+/// Add a respository config via http, in JSON format, which contains the different repository metadata and URLs.
+pub struct RepoAddUrlCommand {
+ /// the expected config.json file format version.
+ #[argh(
+ option,
+ short = 'f',
+ default = "RepoConfigFormat::Version2",
+ from_str_fn(repo_config_format)
+ )]
+ pub format: RepoConfigFormat,
+ /// name of the source (a name from the URL will be derived if not provided).
+ #[argh(option, short = 'n')]
+ pub name: Option<String>,
+ /// http(s) URL pointing to a respository config file, in JSON format, which contains the different repository metadata and URLs.
+ #[argh(positional)]
+ pub repo_url: String,
+}
+
+#[derive(FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "rm")]
/// Remove a configured source repository.
pub struct RepoRemoveCommand {
@@ -243,6 +295,14 @@
serde_json::from_str(&config).map_err(|e| e.to_string())
}
+fn repo_config_format(value: &str) -> Result<RepoConfigFormat, String> {
+ match value {
+ "1" => Ok(RepoConfigFormat::Version1),
+ "2" => Ok(RepoConfigFormat::Version2),
+ _ => Err(format!("unknown format {:?}", value)),
+ }
+}
+
#[cfg(test)]
mod tests {
use {super::*, matches::assert_matches};
@@ -321,17 +381,55 @@
check(&["repo", "-v"], RepoCommand { verbose: true, subcommand: None });
check(&["repo", "--verbose"], RepoCommand { verbose: true, subcommand: None });
check(
- &["repo", "add", "-f", "foo"],
+ &["repo", "add", "file", "foo"],
RepoCommand {
verbose: false,
- subcommand: Some(RepoSubCommand::Add(RepoAddCommand { file: "foo".into() })),
+ subcommand: Some(RepoSubCommand::Add(RepoAddCommand {
+ subcommand: RepoAddSubCommand::File(RepoAddFileCommand {
+ format: RepoConfigFormat::Version2,
+ name: None,
+ file: "foo".into(),
+ }),
+ })),
},
);
check(
- &["repo", "add", "--file", "foo"],
+ &["repo", "add", "file", "-f", "1", "foo"],
RepoCommand {
verbose: false,
- subcommand: Some(RepoSubCommand::Add(RepoAddCommand { file: "foo".into() })),
+ subcommand: Some(RepoSubCommand::Add(RepoAddCommand {
+ subcommand: RepoAddSubCommand::File(RepoAddFileCommand {
+ format: RepoConfigFormat::Version1,
+ name: None,
+ file: "foo".into(),
+ }),
+ })),
+ },
+ );
+ check(
+ &["repo", "add", "file", "-n", "devhost", "foo"],
+ RepoCommand {
+ verbose: false,
+ subcommand: Some(RepoSubCommand::Add(RepoAddCommand {
+ subcommand: RepoAddSubCommand::File(RepoAddFileCommand {
+ format: RepoConfigFormat::Version2,
+ name: Some("devhost".to_string()),
+ file: "foo".into(),
+ }),
+ })),
+ },
+ );
+ check(
+ &["repo", "add", "url", "-n", "devhost", "http://foo.tld/fuchsia/config.json"],
+ RepoCommand {
+ verbose: false,
+ subcommand: Some(RepoSubCommand::Add(RepoAddCommand {
+ subcommand: RepoAddSubCommand::Url(RepoAddUrlCommand {
+ format: RepoConfigFormat::Version2,
+ name: Some("devhost".to_string()),
+ repo_url: "http://foo.tld/fuchsia/config.json".into(),
+ }),
+ })),
},
);
check(
diff --git a/src/sys/pkg/bin/pkgctl/src/main.rs b/src/sys/pkg/bin/pkgctl/src/main.rs
index 02d57da..74c60ab 100644
--- a/src/sys/pkg/bin/pkgctl/src/main.rs
+++ b/src/sys/pkg/bin/pkgctl/src/main.rs
@@ -6,11 +6,14 @@
crate::args::{
Args, Command, ExperimentCommand, ExperimentDisableCommand, ExperimentEnableCommand,
ExperimentSubCommand, GcCommand, GetHashCommand, OpenCommand, PkgStatusCommand,
- RepoAddCommand, RepoCommand, RepoRemoveCommand, RepoSubCommand, ResolveCommand,
- RuleClearCommand, RuleCommand, RuleDumpDynamicCommand, RuleListCommand, RuleReplaceCommand,
+ RepoAddCommand, RepoAddFileCommand, RepoAddSubCommand, RepoAddUrlCommand, RepoCommand,
+ RepoConfigFormat, RepoRemoveCommand, RepoSubCommand, ResolveCommand, RuleClearCommand,
+ RuleCommand, RuleDumpDynamicCommand, RuleListCommand, RuleReplaceCommand,
RuleReplaceFileCommand, RuleReplaceJsonCommand, RuleReplaceSubCommand, RuleSubCommand,
},
+ crate::v1repoconf::SourceConfig,
anyhow::{bail, format_err, Context as _},
+ fidl_fuchsia_net_http::{self as http},
fidl_fuchsia_pkg::{
PackageCacheMarker, PackageResolverAdminMarker, PackageResolverMarker, PackageUrl,
RepositoryManagerMarker, RepositoryManagerProxy,
@@ -22,6 +25,7 @@
files_async, fuchsia_async as fasync,
fuchsia_component::client::connect_to_service,
fuchsia_zircon as zx,
+ futures::io::copy,
futures::stream::TryStreamExt,
std::{
convert::{TryFrom, TryInto},
@@ -35,6 +39,7 @@
mod args;
mod error;
+mod v1repoconf;
pub fn main() -> Result<(), anyhow::Error> {
let mut executor = fasync::Executor::new()?;
@@ -167,12 +172,55 @@
}
Ok(0)
}
- Some(RepoSubCommand::Add(RepoAddCommand { file })) => {
- let repo: RepositoryConfig =
- serde_json::from_reader(io::BufReader::new(File::open(file)?))?;
+ Some(RepoSubCommand::Add(RepoAddCommand { subcommand })) => {
+ match subcommand {
+ RepoAddSubCommand::File(RepoAddFileCommand { format, name, file }) => {
+ let res = match format {
+ RepoConfigFormat::Version1 => {
+ let mut repo: SourceConfig = serde_json::from_reader(
+ io::BufReader::new(File::open(file)?),
+ )?;
+ // If a name is specified via the command line, override the
+ // automatically derived name.
+ if let Some(n) = name {
+ repo.set_id(&n);
+ }
+ let r = repo_manager.add(repo.into()).await?;
+ r
+ }
+ RepoConfigFormat::Version2 => {
+ let repo: RepositoryConfig = serde_json::from_reader(
+ io::BufReader::new(File::open(file)?),
+ )?;
+ let r = repo_manager.add(repo.into()).await?;
+ r
+ }
+ };
- let res = repo_manager.add(repo.into()).await?;
- let () = res.map_err(zx::Status::from_raw)?;
+ let () = res.map_err(zx::Status::from_raw)?;
+ }
+ RepoAddSubCommand::Url(RepoAddUrlCommand { format, name, repo_url }) => {
+ let res = fetch_url(repo_url).await?;
+ let res = match format {
+ RepoConfigFormat::Version1 => {
+ let mut repo: SourceConfig = serde_json::from_slice(&res)?;
+ // If a name is specified via the command line, override the
+ // automatically derived name.
+ if let Some(n) = name {
+ repo.set_id(&n);
+ }
+ let r = repo_manager.add(repo.into()).await?;
+ r
+ }
+ RepoConfigFormat::Version2 => {
+ let repo: RepositoryConfig = serde_json::from_slice(&res)?;
+ let r = repo_manager.add(repo.into()).await?;
+ r
+ }
+ };
+ let () = res.map_err(zx::Status::from_raw)?;
+ }
+ }
Ok(0)
}
@@ -368,3 +416,44 @@
.map(|repo| RepositoryConfig::try_from(repo).map_err(|e| anyhow::Error::from(e)))
.collect()
}
+
+async fn fetch_url<T: Into<String>>(url_string: T) -> Result<Vec<u8>, anyhow::Error> {
+ let http_svc = connect_to_service::<http::LoaderMarker>()
+ .context("Unable to connect to fuchsia.net.http.Loader")?;
+
+ let url_request = http::Request {
+ url: Some(url_string.into()),
+ method: Some(String::from("GET")),
+ headers: None,
+ body: None,
+ deadline: None,
+ ..http::Request::EMPTY
+ };
+
+ let response =
+ http_svc.fetch(url_request).await.context("Error while calling Loader::Fetch")?;
+
+ if let Some(e) = response.error {
+ return Err(format_err!("LoaderProxy error - {:?}", e));
+ }
+
+ let socket = match response.body {
+ Some(s) => fasync::Socket::from_socket(s).context("Error while wrapping body socket")?,
+ _ => {
+ return Err(format_err!("failed to read UrlBody from the stream"));
+ }
+ };
+
+ let mut body = Vec::new();
+ let bytes_received =
+ copy(socket, &mut body).await.context("Failed to read bytes from the socket")?;
+
+ if bytes_received < 1 {
+ return Err(format_err!(
+ "Failed to download data from url! bytes_received = {}",
+ bytes_received
+ ));
+ }
+
+ Ok(body)
+}
diff --git a/src/sys/pkg/bin/pkgctl/src/v1repoconf.rs b/src/sys/pkg/bin/pkgctl/src/v1repoconf.rs
new file mode 100644
index 0000000..929bd12
--- /dev/null
+++ b/src/sys/pkg/bin/pkgctl/src/v1repoconf.rs
@@ -0,0 +1,131 @@
+// Copyright 2021 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.
+
+// This is a temporary solution to use v1 repository configs in pkgctl
+// until there is no longer a need to accept v1 repository configs.
+
+use {
+ fidl_fuchsia_pkg as fidl,
+ serde::{Deserialize, Serialize},
+ std::borrow::Cow,
+};
+
+#[derive(Debug, Serialize, Deserialize)]
+#[allow(non_snake_case)]
+pub struct KeyConfig {
+ r#type: String,
+ #[serde(with = "hex_serde")]
+ value: Vec<u8>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[allow(non_snake_case)]
+pub struct StatusConfig {
+ Enabled: bool,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[allow(non_snake_case)]
+pub struct BlobEncryptionKey {
+ #[serde(with = "hex_serde")]
+ Data: Vec<u8>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[allow(non_snake_case)]
+pub struct SourceConfig {
+ ID: String,
+ RepoURL: String,
+ BlobRepoURL: String,
+ RatePeriod: i64,
+ RootKeys: Vec<KeyConfig>,
+ #[serde(default = "default_root_version")]
+ rootVersion: u32,
+ #[serde(default = "default_root_threshold")]
+ rootThreshold: u32,
+ StatusConfig: StatusConfig,
+ Auto: bool,
+ BlobKey: Option<BlobEncryptionKey>,
+}
+
+impl SourceConfig {
+ pub fn set_id(&mut self, id: &str) {
+ self.ID = id.to_string();
+ }
+}
+
+impl From<SourceConfig> for fidl::RepositoryConfig {
+ fn from(config: SourceConfig) -> Self {
+ fidl::RepositoryConfig {
+ repo_url: Some(format_repo_url(&config.ID)),
+ root_version: Some(config.rootVersion),
+ root_threshold: Some(config.rootThreshold),
+ root_keys: Some(config.RootKeys.into_iter().map(|key| key.into()).collect()),
+ mirrors: Some({
+ [fidl::MirrorConfig {
+ mirror_url: Some(config.RepoURL),
+ subscribe: Some(config.Auto),
+ ..fidl::MirrorConfig::EMPTY
+ }]
+ .to_vec()
+ }),
+ ..fidl::RepositoryConfig::EMPTY
+ }
+ }
+}
+
+impl From<KeyConfig> for fidl::RepositoryKeyConfig {
+ fn from(key: KeyConfig) -> Self {
+ match key.r#type.as_str() {
+ "ed25519" => fidl::RepositoryKeyConfig::Ed25519Key(key.value),
+ _ => fidl::RepositoryKeyConfig::unknown(0, Default::default()),
+ }
+ }
+}
+
+fn default_root_version() -> u32 {
+ 1
+}
+
+fn default_root_threshold() -> u32 {
+ 1
+}
+
+fn format_repo_url<'a>(url: &'a str) -> String {
+ // If the canonical prefix was already part of the command line argument provided,
+ // don't sanitize this prefix part of the string.
+ let id = if let Some(u) = url.strip_prefix("fuchsia-pkg://") { u } else { url };
+ return format!("fuchsia-pkg://{}", sanitize_id(id));
+}
+
+fn sanitize_id<'a>(id: &'a str) -> Cow<'a, str> {
+ return id
+ .chars()
+ .map(|c| match c {
+ 'A'..='Z' | 'a'..='z' | '0'..='9' | '-' => c,
+ _ => '_',
+ })
+ .collect();
+}
+
+mod hex_serde {
+ use {hex, serde::Deserialize};
+
+ pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ let s = hex::encode(bytes);
+ serializer.serialize_str(&s)
+ }
+
+ pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let value = String::deserialize(deserializer)?;
+ hex::decode(value.as_bytes())
+ .map_err(|e| serde::de::Error::custom(format!("bad hex value: {:?}: {}", value, e)))
+ }
+}
diff --git a/src/sys/pkg/lib/repo/add.go b/src/sys/pkg/lib/repo/add.go
index c61ad0b..9c62136 100644
--- a/src/sys/pkg/lib/repo/add.go
+++ b/src/sys/pkg/lib/repo/add.go
@@ -83,7 +83,7 @@
}
func repoAddCmd(file string) []string {
- return []string{"pkgctl", "repo", "add", "--file", file}
+ return []string{"pkgctl", "repo", "add", "file", file}
}
type shell interface {
diff --git a/src/sys/pkg/tests/pkgctl/BUILD.gn b/src/sys/pkg/tests/pkgctl/BUILD.gn
index b73c8b6..d27ecf4 100644
--- a/src/sys/pkg/tests/pkgctl/BUILD.gn
+++ b/src/sys/pkg/tests/pkgctl/BUILD.gn
@@ -10,14 +10,18 @@
edition = "2018"
deps = [
+ "//sdk/fidl/fuchsia.net:fuchsia.net-rustc",
+ "//sdk/fidl/fuchsia.net.http:fuchsia.net.http-rustc",
"//sdk/fidl/fuchsia.pkg:fuchsia.pkg-rustc",
"//sdk/fidl/fuchsia.pkg.rewrite:fuchsia.pkg.rewrite-rustc",
+ "//sdk/fidl/fuchsia.posix.socket:fuchsia.posix.socket-rustc",
"//sdk/fidl/fuchsia.space:fuchsia.space-rustc",
"//sdk/fidl/fuchsia.sys:fuchsia.sys-rustc",
"//src/lib/fidl/rust/fidl",
"//src/lib/fuchsia-async",
"//src/lib/fuchsia-component",
"//src/lib/fuchsia-url",
+ "//src/lib/testing/fuchsia-hyper-test-support",
"//src/lib/zircon/rust:fuchsia-zircon",
"//src/sys/lib/fidl-fuchsia-pkg-ext",
"//src/sys/lib/fidl-fuchsia-pkg-rewrite-ext",
diff --git a/src/sys/pkg/tests/pkgctl/meta/pkgctl-integration-test.cmx b/src/sys/pkg/tests/pkgctl/meta/pkgctl-integration-test.cmx
index ee78aee3..0a58067 100644
--- a/src/sys/pkg/tests/pkgctl/meta/pkgctl-integration-test.cmx
+++ b/src/sys/pkg/tests/pkgctl/meta/pkgctl-integration-test.cmx
@@ -1,6 +1,17 @@
{
+ "facets": {
+ "fuchsia.test": {
+ "injected-services": {
+ "fuchsia.net.NameLookup": "fuchsia-pkg://fuchsia.com/dns-resolver#meta/dns-resolver.cmx",
+ "fuchsia.net.http.Loader": "fuchsia-pkg://fuchsia.com/http-client#meta/http-client.cmx",
+ "fuchsia.net.routes.State": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx",
+ "fuchsia.posix.socket.Provider": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx"
+ }
+ }
+ },
"include": [
- "sdk/lib/diagnostics/syslog/client.shard.cmx"
+ "sdk/lib/diagnostics/syslog/client.shard.cmx",
+ "src/lib/fuchsia-hyper/hyper.shard.cmx"
],
"program": {
"binary": "test/pkgctl-integration-test"
@@ -10,6 +21,7 @@
"isolated-temp"
],
"services": [
+ "fuchsia.net.http.Loader",
"fuchsia.sys.Environment",
"fuchsia.sys.Launcher",
"fuchsia.sys.Loader"
diff --git a/src/sys/pkg/tests/pkgctl/src/lib.rs b/src/sys/pkg/tests/pkgctl/src/lib.rs
index a9e3fae..3711d7c 100644
--- a/src/sys/pkg/tests/pkgctl/src/lib.rs
+++ b/src/sys/pkg/tests/pkgctl/src/lib.rs
@@ -26,6 +26,7 @@
client::{AppBuilder, Output},
server::{NestedEnvironment, ServiceFs},
},
+ fuchsia_hyper_test_support::{handler::StaticResponse, TestServer},
fuchsia_url::pkg_url::{PkgUrl, RepoUrl},
fuchsia_zircon::Status,
futures::prelude::*,
@@ -34,6 +35,7 @@
std::{
convert::TryFrom,
fs::{create_dir, File},
+ io::Write,
iter::FusedIterator,
path::PathBuf,
sync::Arc,
@@ -59,6 +61,7 @@
fn new() -> Self {
let mut fs = ServiceFs::new();
+ fs.add_proxy_service::<fidl_fuchsia_net_http::LoaderMarker, _>();
let package_resolver = Arc::new(MockPackageResolverService::new());
let package_resolver_clone = package_resolver.clone();
@@ -488,6 +491,40 @@
.build()
}
+// This builds a v2 RepositoryConfig that is expected to be in the repository manager add request
+// when pkgctl is provided with the V1_LEGACY_TEST_REPO_JSON structure below as input.
+fn make_v1_legacy_expected_test_repo_config() -> RepositoryConfig {
+ RepositoryConfigBuilder::new(RepoUrl::new("legacy_repo".to_string()).expect("valid url"))
+ .add_root_key(RepositoryKey::Ed25519(vec![0u8]))
+ .add_mirror(
+ MirrorConfigBuilder::new("http://legacy.org".parse::<Uri>().unwrap())
+ .unwrap()
+ .subscribe(true)
+ .build(),
+ )
+ .build()
+}
+
+const V1_LEGACY_TEST_REPO_JSON: &str = r#"{
+ "ID":"legacy_repo",
+ "RepoURL":"http://legacy.org",
+ "BlobRepoURL":"http://legacy.org/blobs",
+ "RatePeriod":60,
+ "RootKeys":[
+ {
+ "type":"ed25519",
+ "value":"00"
+ }
+ ],
+ "rootVersion":1,
+ "rootThreshold":1,
+ "StatusConfig":{
+ "Enabled":true
+ },
+ "Auto":true,
+ "BlobKey":null
+}"#;
+
#[fasync::run_singlethreaded(test)]
async fn test_repo() {
let env = TestEnv::new();
@@ -578,17 +615,38 @@
}
macro_rules! repo_add_tests {
- ($($test_name:ident: $flag:expr,)*) => {
+ ($($test_name:ident: $source:expr, $version:expr,)*) => {
$(
#[fasync::run_singlethreaded(test)]
async fn $test_name() {
let env = TestEnv::new();
- let repo_config = make_test_repo_config();
- let f =
- File::create(env.repo_config_arg_path.join("the-config")).expect("create repo config file");
- serde_json::to_writer(f, &repo_config).expect("write RepositoryConfig json");
- let output = env.run_pkgctl(vec!["repo", "add", $flag, "/repo-configs/the-config"]).await;
+ let repo_config = match $version {
+ "1" => make_v1_legacy_expected_test_repo_config(),
+ _ => make_test_repo_config(),
+ };
+
+ let output = match $source {
+ "file" => {
+ let mut f =
+ File::create(env.repo_config_arg_path.join("the-config")).expect("create repo config file");
+ match $version {
+ "1" => { f.write_all(V1_LEGACY_TEST_REPO_JSON.as_bytes()).expect("write v1 SourceConfig json"); },
+ _ => { serde_json::to_writer(f, &repo_config).expect("write RepositoryConfig json"); }
+ };
+ env.run_pkgctl(vec!["repo", "add", $source, "-f", $version, "/repo-configs/the-config"]).await
+ },
+ "url" => {
+ let response = match $version {
+ "1" => StaticResponse::ok_body(V1_LEGACY_TEST_REPO_JSON),
+ _ => StaticResponse::ok_body(serde_json::to_string(&repo_config).unwrap()),
+ };
+ let server = TestServer::builder().handler(response).start();
+ env.run_pkgctl(vec!["repo", "add", $source, "-f", $version, server.local_url_for_path("some/path").as_str()]).await
+ },
+ // Unsupported source
+ _ => env.run_pkgctl(vec!["repo", "add", $source, "-f", $version]).await,
+ };
assert_stdout(&output, "");
env.assert_only_repository_manager_called_with(vec![CapturedRepositoryManagerRequest::Add {
@@ -600,8 +658,10 @@
}
repo_add_tests! {
- test_repo_add_short: "-f",
- test_repo_add_long: "--file",
+ test_repo_add_v1_file: "file", "1",
+ test_repo_add_v2_file: "file", "2",
+ test_repo_add_v1_url: "url", "1",
+ test_repo_add_v2_url: "url", "2",
}
#[fasync::run_singlethreaded(test)]