use crate::datetime::parse_datetime;
use crate::error::{self, Result};
use crate::source::parse_key_source;
use clap::Parser;
use jiff::Timestamp;
use snafu::ResultExt;
use std::num::NonZeroU64;
use std::path::{Path, PathBuf};
use tough::editor::RepositoryEditor;
use tough::{ExpirationEnforcement, RepositoryLoader};
use url::Url;
#[derive(Debug, Parser)]
pub(crate) struct TransferMetadataArgs {
#[arg(long)]
allow_expired_repo: bool,
#[arg(short, long = "key", required = true)]
keys: Vec<String>,
#[arg(short, long = "metadata-url")]
metadata_base_url: Url,
#[arg(short, long = "new-root")]
new_root: PathBuf,
#[arg(short, long)]
outdir: PathBuf,
#[arg(short = 'r', long = "current-root")]
current_root: PathBuf,
#[arg(long = "snapshot-expires", value_parser = parse_datetime)]
snapshot_expires: Timestamp,
#[arg(long = "snapshot-version")]
snapshot_version: NonZeroU64,
#[arg(short, long = "targets-url")]
targets_base_url: Url,
#[arg(long = "targets-expires", value_parser = parse_datetime)]
targets_expires: Timestamp,
#[arg(long = "targets-version")]
targets_version: NonZeroU64,
#[arg(long = "timestamp-expires", value_parser = parse_datetime)]
timestamp_expires: Timestamp,
#[arg(long = "timestamp-version")]
timestamp_version: NonZeroU64,
}
fn expired_repo_warning<P: AsRef<Path>>(from_path: P, to_path: P) {
#[rustfmt::skip]
eprintln!("\
=================================================================
Transferring metadata from {} to {}
WARNING: `--allow-expired-repo` was passed; this is unsafe and will not establish trust, use only for testing!
=================================================================",
from_path.as_ref().display(),
to_path.as_ref().display());
}
impl TransferMetadataArgs {
pub(crate) async fn run(&self) -> Result<()> {
let mut keys = Vec::new();
for source in &self.keys {
let key_source = parse_key_source(source)?;
keys.push(key_source);
}
let current_root = &self.current_root;
let new_root = &self.new_root;
let expiration_enforcement = if self.allow_expired_repo {
expired_repo_warning(¤t_root, &new_root);
ExpirationEnforcement::Unsafe
} else {
ExpirationEnforcement::Safe
};
let current_repo = RepositoryLoader::new(
&tokio::fs::read(current_root)
.await
.context(error::OpenRootSnafu {
path: ¤t_root,
})?,
self.metadata_base_url.clone(),
self.targets_base_url.clone(),
)
.expiration_enforcement(expiration_enforcement)
.load()
.await
.context(error::RepoLoadSnafu)?;
let mut editor = RepositoryEditor::new(new_root)
.await
.context(error::EditorCreateSnafu { path: &new_root })?;
editor
.targets_version(self.targets_version)
.context(error::DelegationStructureSnafu)?
.targets_expires(self.targets_expires)
.context(error::DelegationStructureSnafu)?
.snapshot_version(self.snapshot_version)
.snapshot_expires(self.snapshot_expires)
.timestamp_version(self.timestamp_version)
.timestamp_expires(self.timestamp_expires);
let targets = current_repo.targets();
for (target_name, target) in &targets.signed.targets {
editor
.add_target(target_name.clone(), target.clone())
.context(error::DelegationStructureSnafu)?;
}
let signed_repo = editor.sign(&keys).await.context(error::SignRepoSnafu)?;
let metadata_dir = &self.outdir.join("metadata");
signed_repo
.write(metadata_dir)
.await
.context(error::WriteRepoSnafu {
directory: metadata_dir,
})?;
Ok(())
}
}