#![deny(rust_2018_idioms)]
#![warn(clippy::pedantic)]
#![allow(
clippy::missing_errors_doc,
// Identifiers like Command::Create are clearer than Self::Create regardless of context
clippy::use_self,
// Caused by interacting with tough::schema::*._extra
clippy::used_underscore_binding,
)]
mod add_key_role;
mod add_role;
mod clone;
mod common;
mod create;
mod create_role;
mod datetime;
mod download;
mod download_root;
mod error;
mod remove_key_role;
mod remove_role;
mod root;
mod source;
mod update;
mod update_targets;
use crate::error::Result;
use clap::Parser;
use rayon::prelude::*;
use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode};
use snafu::{ErrorCompat, OptionExt, ResultExt};
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use tempfile::NamedTempFile;
use tough::schema::Target;
use tough::TargetName;
use walkdir::WalkDir;
static SPEC_VERSION: &str = "1.0.0";
#[derive(Parser)]
struct Program {
#[clap(
name = "log-level",
short = 'l',
long = "log-level",
default_value = "info"
)]
log_level: LevelFilter,
#[clap(subcommand)]
cmd: Command,
}
impl Program {
fn run(self) -> Result<()> {
TermLogger::init(
self.log_level,
ConfigBuilder::new()
.add_filter_allow_str("tuftool")
.add_filter_allow_str("tough")
.build(),
TerminalMode::Mixed,
ColorChoice::Auto,
)
.context(error::LoggerSnafu)?;
self.cmd.run()
}
}
#[derive(Debug, Parser)]
enum Command {
Create(create::CreateArgs),
Download(download::DownloadArgs),
Update(Box<update::UpdateArgs>),
#[clap(subcommand)]
Root(root::Command),
Delegation(Delegation),
Clone(clone::CloneArgs),
}
impl Command {
fn run(self) -> Result<()> {
match self {
Command::Create(args) => args.run(),
Command::Root(root_subcommand) => root_subcommand.run(),
Command::Download(args) => args.run(),
Command::Update(args) => args.run(),
Command::Delegation(cmd) => cmd.run(),
Command::Clone(cmd) => cmd.run(),
}
}
}
fn load_file<T>(path: &Path) -> Result<T>
where
for<'de> T: serde::Deserialize<'de>,
{
serde_json::from_reader(File::open(path).context(error::FileOpenSnafu { path })?)
.context(error::FileParseJsonSnafu { path })
}
fn write_file<T>(path: &Path, json: &T) -> Result<()>
where
T: serde::Serialize,
{
let parent = path.parent().context(error::PathParentSnafu { path })?;
let mut writer =
NamedTempFile::new_in(parent).context(error::FileTempCreateSnafu { path: parent })?;
serde_json::to_writer_pretty(&mut writer, json).context(error::FileWriteJsonSnafu { path })?;
writer
.write_all(b"\n")
.context(error::FileWriteSnafu { path })?;
writer
.persist(path)
.context(error::FilePersistSnafu { path })?;
Ok(())
}
fn build_targets<P>(indir: P, follow_links: bool) -> Result<HashMap<TargetName, Target>>
where
P: AsRef<Path>,
{
let indir = indir.as_ref();
WalkDir::new(indir)
.follow_links(follow_links)
.into_iter()
.par_bridge()
.filter_map(|entry| match entry {
Ok(entry) => {
if entry.file_type().is_file() {
Some(process_target(entry.path()))
} else {
None
}
}
Err(err) => Some(Err(err).context(error::WalkDirSnafu { directory: indir })),
})
.collect()
}
fn process_target(path: &Path) -> Result<(TargetName, Target)> {
let target_name = TargetName::new(
path.file_name()
.context(error::NoFileNameSnafu { path })?
.to_str()
.context(error::PathUtf8Snafu { path })?,
)
.context(error::InvalidTargetNameSnafu)?;
let target = Target::from_path(path).context(error::TargetFromPathSnafu { path })?;
Ok((target_name, target))
}
fn main() -> ! {
std::process::exit(match Program::from_args().run() {
Ok(()) => 0,
Err(err) => {
eprintln!("{}", err);
if let Some(var) = std::env::var_os("RUST_BACKTRACE") {
if var != "0" {
if let Some(backtrace) = err.backtrace() {
eprintln!("\n{:?}", backtrace);
}
}
}
1
}
})
}
#[derive(Parser, Debug)]
struct Delegation {
#[clap(long = "signing-role", required = true)]
role: String,
#[clap(subcommand)]
cmd: DelegationCommand,
}
impl Delegation {
fn run(self) -> Result<()> {
self.cmd.run(&self.role)
}
}
#[derive(Debug, Parser)]
enum DelegationCommand {
CreateRole(Box<create_role::CreateRoleArgs>),
AddRole(Box<add_role::AddRoleArgs>),
UpdateDelegatedTargets(Box<update_targets::UpdateTargetsArgs>),
AddKey(Box<add_key_role::AddKeyArgs>),
RemoveKey(Box<remove_key_role::RemoveKeyArgs>),
Remove(Box<remove_role::RemoveRoleArgs>),
}
impl DelegationCommand {
fn run(self, role: &str) -> Result<()> {
match self {
DelegationCommand::CreateRole(args) => args.run(role),
DelegationCommand::AddRole(args) => args.run(role),
DelegationCommand::UpdateDelegatedTargets(args) => args.run(role),
DelegationCommand::AddKey(args) => args.run(role),
DelegationCommand::RemoveKey(args) => args.run(role),
DelegationCommand::Remove(args) => args.run(role),
}
}
}