use std::error::Error;
use std::fmt::{Debug, Display, Formatter, Result as FResult};
use std::path::PathBuf;
use std::process::{Command, ExitStatus};
use brane_cfg::node::NodeKind;
use brane_shr::formatters::Capitalizeable;
use brane_tsk::docker::ImageSource;
use console::style;
use enum_debug::EnumDebug as _;
use jsonwebtoken::jwk::KeyAlgorithm;
use specifications::container::Image;
use specifications::version::Version;
#[derive(Debug)]
pub enum DownloadError {
CachedirTagCreate { path: PathBuf, err: std::io::Error },
CachedirTagWrite { path: PathBuf, err: std::io::Error },
DirNotFound { what: &'static str, path: PathBuf },
DirNotADir { what: &'static str, path: PathBuf },
DirCreateError { what: &'static str, path: PathBuf, err: std::io::Error },
TempDirError { err: std::io::Error },
DownloadError { address: String, path: PathBuf, err: Box<brane_shr::fs::Error> },
UnarchiveError { tar: PathBuf, target: PathBuf, err: Box<brane_shr::fs::Error> },
ReadDirError { path: PathBuf, err: std::io::Error },
ReadEntryError { path: PathBuf, entry: usize, err: std::io::Error },
MoveError { source: PathBuf, target: PathBuf, err: Box<brane_shr::fs::Error> },
DockerConnectError { err: brane_tsk::docker::Error },
PullError { name: String, image: String, err: brane_tsk::docker::Error },
SaveError { name: String, image: String, path: PathBuf, err: brane_tsk::docker::Error },
}
impl Display for DownloadError {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use self::DownloadError::*;
match self {
CachedirTagCreate { path, .. } => write!(f, "Failed to create CACHEDIR.TAG file '{}'", path.display()),
CachedirTagWrite { path, .. } => write!(f, "Failed to write to CACHEDIR.TAG file '{}'", path.display()),
DirNotFound { what, path } => write!(f, "{} directory '{}' not found", what.capitalize(), path.display()),
DirNotADir { what, path } => write!(f, "{} directory '{}' exists but is not a directory", what.capitalize(), path.display()),
DirCreateError { what, path, .. } => write!(f, "Failed to create {} directory '{}'", what, path.display()),
TempDirError { .. } => write!(f, "Failed to create a temporary directory"),
DownloadError { address, path, .. } => write!(f, "Failed to download '{}' to '{}'", address, path.display()),
UnarchiveError { tar, target, .. } => write!(f, "Failed to unpack '{}' to '{}'", tar.display(), target.display()),
ReadDirError { path, .. } => write!(f, "Failed to read directory '{}'", path.display()),
ReadEntryError { path, entry, .. } => write!(f, "Failed to read entry {} in directory '{}'", entry, path.display()),
MoveError { source, target, .. } => write!(f, "Failed to move '{}' to '{}'", source.display(), target.display()),
DockerConnectError { .. } => write!(f, "Failed to connect to local Docker daemon"),
PullError { name, image, .. } => write!(f, "Failed to pull '{image}' as '{name}'"),
SaveError { name, path, .. } => write!(f, "Failed to save image '{}' to '{}'", name, path.display()),
}
}
}
impl Error for DownloadError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use self::DownloadError::*;
match self {
CachedirTagCreate { err, .. } => Some(err),
CachedirTagWrite { err, .. } => Some(err),
DirNotFound { .. } => None,
DirNotADir { .. } => None,
DirCreateError { err, .. } => Some(err),
TempDirError { err } => Some(err),
DownloadError { err, .. } => Some(err),
UnarchiveError { err, .. } => Some(err),
ReadDirError { err, .. } => Some(err),
ReadEntryError { err, .. } => Some(err),
MoveError { err, .. } => Some(err),
DockerConnectError { err } => Some(err),
PullError { err, .. } => Some(err),
SaveError { err, .. } => Some(err),
}
}
}
#[derive(Debug)]
pub enum GenerateError {
DirNotFound { path: PathBuf },
DirNotADir { path: PathBuf },
DirCreateError { path: PathBuf, err: std::io::Error },
CanonicalizeError { path: PathBuf, err: std::io::Error },
FileNotAFile { path: PathBuf },
FileWriteError { what: &'static str, path: PathBuf, err: std::io::Error },
FileSerializeError { what: &'static str, path: PathBuf, err: serde_json::Error },
FileDeserializeError { what: &'static str, path: PathBuf, err: serde_json::Error },
ExtractError { what: &'static str, path: PathBuf, err: std::io::Error },
ExecutableError { err: Box<brane_shr::fs::Error> },
FileMetadataError { what: &'static str, path: PathBuf, err: std::io::Error },
FilePermissionsError { what: &'static str, path: PathBuf, err: std::io::Error },
FileChecksumError { path: PathBuf, expected: String, got: String },
ConfigSerializeError { err: serde_json::Error },
SpawnError { cmd: Command, err: std::io::Error },
SpawnFailure { cmd: Command, status: ExitStatus, err: String },
CaCertNotFound { path: PathBuf },
CaCertNotAFile { path: PathBuf },
CaKeyNotFound { path: PathBuf },
CaKeyNotAFile { path: PathBuf },
FileOpenError { what: &'static str, path: PathBuf, err: std::io::Error },
CopyError { source: PathBuf, target: PathBuf, err: std::io::Error },
FileCreateError { what: &'static str, path: PathBuf, err: std::io::Error },
FileHeaderWriteError { what: &'static str, path: PathBuf, err: std::io::Error },
FileBodyWriteError { what: &'static str, path: PathBuf, err: brane_cfg::info::YamlError },
UnknownLocation { loc: String },
TempDirError { err: std::io::Error },
RepoDownloadError { repo: String, target: PathBuf, err: brane_shr::fs::Error },
RepoUnpackError { tar: PathBuf, target: PathBuf, err: brane_shr::fs::Error },
RepoRecurseError { target: PathBuf, err: brane_shr::fs::Error },
MigrationsRetrieve { path: PathBuf, err: diesel_migrations::MigrationError },
DatabaseConnect { path: PathBuf, err: diesel::ConnectionError },
MigrationsApply { path: PathBuf, err: Box<dyn 'static + Error> },
UnsupportedKeyAlgorithm { key_alg: KeyAlgorithm },
TokenGenerate { err: specifications::policy::Error },
}
impl Display for GenerateError {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use GenerateError::*;
match self {
DirNotFound { path } => write!(f, "Directory '{}' not found", path.display()),
DirNotADir { path } => write!(f, "Directory '{}' exists but not as a directory", path.display()),
DirCreateError { path, .. } => write!(f, "Failed to create directory '{}'", path.display()),
CanonicalizeError { path, .. } => write!(f, "Failed to canonicalize path '{}'", path.display()),
FileNotAFile { path } => write!(f, "File '{}' exists but not as a file", path.display()),
FileWriteError { what, path, .. } => write!(f, "Failed to write to {} file '{}'", what, path.display()),
FileSerializeError { what, path, .. } => write!(f, "Failed to write JSON to {} file '{}'", what, path.display()),
FileDeserializeError { what, path, .. } => write!(f, "Failed to read JSON from {} file '{}'", what, path.display()),
ExtractError { what, path, .. } => write!(f, "Failed to extract embedded {}-binary to '{}'", what, path.display()),
ExecutableError { .. } => write!(f, "Failed to make file executable"),
FileMetadataError { what, path, .. } => write!(f, "Failed to get metadata of {} file '{}'", what, path.display()),
FilePermissionsError { what, path, .. } => write!(f, "Failed to set permissions of {} file '{}'", what, path.display()),
FileChecksumError { path, .. } => {
write!(f, "File '{}' had unexpected checksum (might indicate the download is no longer valid)", path.display())
},
ConfigSerializeError { .. } => write!(f, "Failed to serialize config"),
SpawnError { cmd, .. } => write!(f, "Failed to run command '{cmd:?}'"),
SpawnFailure { cmd, status, err } => write!(
f,
"Command '{:?}' failed{}\n\nstderr:\n{}\n\n",
cmd,
if let Some(code) = status.code() { format!(" with exit code {code}") } else { String::new() },
err
),
CaCertNotFound { path } => write!(f, "Certificate authority's certificate '{}' not found", path.display()),
CaCertNotAFile { path } => write!(f, "Certificate authority's certificate '{}' exists but is not a file", path.display()),
CaKeyNotFound { path } => write!(f, "Certificate authority's private key '{}' not found", path.display()),
CaKeyNotAFile { path } => write!(f, "Certificate authority's private key '{}' exists but is not a file", path.display()),
FileOpenError { what, path, .. } => write!(f, "Failed to open {} file '{}'", what, path.display()),
CopyError { source, target, .. } => write!(f, "Failed to write '{}' to '{}'", source.display(), target.display()),
FileCreateError { what, path, .. } => write!(f, "Failed to create new {} file '{what}'", path.display()),
FileHeaderWriteError { what, path, .. } => write!(f, "Failed to write header to {} file '{what}'", path.display()),
FileBodyWriteError { what, .. } => write!(f, "Failed to write body to {what} file"),
UnknownLocation { loc } => write!(f, "Unknown location '{loc}' (did you forget to specify it in the LOCATIONS argument?)"),
TempDirError { .. } => write!(f, "Failed to create temporary directory in system temp folder"),
RepoDownloadError { repo, target, .. } => write!(f, "Failed to download repository archive '{}' to '{}'", repo, target.display()),
RepoUnpackError { tar, target, .. } => write!(f, "Failed to unpack repository archive '{}' to '{}'", tar.display(), target.display()),
RepoRecurseError { target, .. } => {
write!(f, "Failed to recurse into only directory of unpacked repository archive '{}'", target.display())
},
MigrationsRetrieve { path, .. } => write!(f, "Failed to find Diesel migrations in '{}'", path.display()),
DatabaseConnect { path, .. } => write!(f, "Failed to connect to SQLite database file '{}'", path.display()),
MigrationsApply { path, .. } => write!(f, "Failed to apply migrations to SQLite database file '{}'", path.display()),
UnsupportedKeyAlgorithm { key_alg } => {
write!(f, "Policy key algorithm {key_alg} is unsupported")
},
TokenGenerate { .. } => write!(f, "Failed to generate new policy token"),
}
}
}
impl Error for GenerateError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use GenerateError::*;
match self {
DirNotFound { .. } => None,
DirNotADir { .. } => None,
DirCreateError { err, .. } => Some(err),
CanonicalizeError { err, .. } => Some(err),
FileNotAFile { .. } => None,
FileWriteError { err, .. } => Some(err),
FileSerializeError { err, .. } => Some(err),
FileDeserializeError { err, .. } => Some(err),
ExtractError { err, .. } => Some(err),
ExecutableError { err } => Some(err),
FileMetadataError { err, .. } => Some(err),
FilePermissionsError { err, .. } => Some(err),
FileChecksumError { .. } => None,
ConfigSerializeError { err } => Some(err),
SpawnError { err, .. } => Some(err),
SpawnFailure { .. } => None,
CaCertNotFound { .. } => None,
CaCertNotAFile { .. } => None,
CaKeyNotFound { .. } => None,
CaKeyNotAFile { .. } => None,
FileOpenError { err, .. } => Some(err),
CopyError { err, .. } => Some(err),
FileCreateError { err, .. } => Some(err),
FileHeaderWriteError { err, .. } => Some(err),
FileBodyWriteError { err, .. } => Some(err),
UnknownLocation { .. } => None,
TempDirError { err } => Some(err),
RepoDownloadError { err, .. } => Some(err),
RepoUnpackError { err, .. } => Some(err),
RepoRecurseError { err, .. } => Some(err),
MigrationsRetrieve { err, .. } => Some(err),
DatabaseConnect { err, .. } => Some(err),
MigrationsApply { err, .. } => Some(&**err),
UnsupportedKeyAlgorithm { .. } => None,
TokenGenerate { err, .. } => Some(err),
}
}
}
#[derive(Debug)]
pub enum LifetimeError {
CanonicalizeError { path: PathBuf, err: std::io::Error },
ExeParseError { raw: String },
DockerComposeNotFound { path: PathBuf },
DockerComposeNotAFile { path: PathBuf },
DockerComposeNotBakedIn { kind: NodeKind, version: Version },
DockerComposeCreateError { path: PathBuf, err: std::io::Error },
DockerComposeWriteError { path: PathBuf, err: std::io::Error },
AuditLogCreate { path: PathBuf, err: std::io::Error },
ProxyReadError { err: brane_cfg::info::YamlError },
HostsFileCreateError { path: PathBuf, err: std::io::Error },
HostsFileWriteError { path: PathBuf, err: serde_yaml::Error },
ImageDigestError { path: PathBuf, err: brane_tsk::docker::Error },
ImageLoadError { image: Box<Image>, source: Box<ImageSource>, err: brane_tsk::docker::Error },
MissingProxyPath,
MissingProxyService,
NodeConfigLoadError { err: brane_cfg::info::YamlError },
DockerConnectError { err: brane_tsk::errors::DockerError },
UnmatchedNodeKind { got: NodeKind, expected: NodeKind },
JobLaunchError { command: Command, err: std::io::Error },
JobFailure { command: Command, status: ExitStatus },
}
impl Display for LifetimeError {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use LifetimeError::*;
match self {
CanonicalizeError { path, .. } => write!(f, "Failed to canonicalize path '{}'", path.display()),
ExeParseError { raw } => write!(f, "Failed to parse '{raw}' as a valid string of bash-arguments"),
DockerComposeNotFound { path } => write!(f, "Docker Compose file '{}' not found", path.display()),
DockerComposeNotAFile { path } => write!(f, "Docker Compose file '{}' exists but is not a file", path.display()),
DockerComposeNotBakedIn { kind, version } => {
write!(f, "No baked-in {kind} Docker Compose for Brane version v{version} exists (give it yourself using '--file')")
},
DockerComposeCreateError { path, .. } => write!(f, "Failed to create Docker Compose file '{}'", path.display()),
DockerComposeWriteError { path, .. } => write!(f, "Failed to write to Docker Compose file '{}'", path.display()),
AuditLogCreate { path, .. } => write!(f, "Failed to touch audit log '{}' into existance", path.display()),
ProxyReadError { .. } => write!(f, "Failed to read proxy config file"),
HostsFileCreateError { path, .. } => write!(f, "Failed to create extra hosts file '{}'", path.display()),
HostsFileWriteError { path, .. } => write!(f, "Failed to write to extra hosts file '{}'", path.display()),
ImageDigestError { path, .. } => write!(f, "Failed to get digest of image {}", style(path.display()).bold()),
ImageLoadError { image, source, .. } => {
write!(f, "Failed to load image {} from '{}'", style(image).bold(), style(source).bold())
},
MissingProxyPath => write!(
f,
"A proxy service specification is given, but not a path to a 'proxy.yml' file. Specify both if you want to host a proxy service in \
this node, or none if you want to use an external one."
),
MissingProxyService => write!(
f,
"A path to a 'proxy.yml' file is given, but not a proxy service specification. Specify both if you want to host a proxy service in \
this node, or none if you want to use an external one."
),
NodeConfigLoadError { .. } => write!(f, "Failed to load node.yml file"),
DockerConnectError { .. } => write!(f, "Failed to connect to local Docker socket"),
UnmatchedNodeKind { got, expected } => {
write!(f, "Got command to start {} node, but 'node.yml' defined a {} node", got.variant(), expected.variant())
},
JobLaunchError { command, .. } => write!(f, "Failed to launch command '{command:?}'"),
JobFailure { command, status } => write!(
f,
"Command '{}' failed with exit code {} (see output above)",
style(format!("{command:?}")).bold(),
style(status.code().map(|c| c.to_string()).unwrap_or_else(|| "non-zero".into())).bold()
),
}
}
}
impl Error for LifetimeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use LifetimeError::*;
match self {
CanonicalizeError { err, .. } => Some(err),
ExeParseError { .. } => None,
DockerComposeNotFound { .. } => None,
DockerComposeNotAFile { .. } => None,
DockerComposeNotBakedIn { .. } => None,
DockerComposeCreateError { err, .. } => Some(err),
DockerComposeWriteError { err, .. } => Some(err),
AuditLogCreate { err, .. } => Some(err),
ProxyReadError { err } => Some(err),
HostsFileCreateError { err, .. } => Some(err),
HostsFileWriteError { err, .. } => Some(err),
ImageDigestError { err, .. } => Some(err),
ImageLoadError { err, .. } => Some(err),
MissingProxyPath => None,
MissingProxyService => None,
NodeConfigLoadError { err } => Some(err),
DockerConnectError { err } => Some(err),
UnmatchedNodeKind { .. } => None,
JobLaunchError { err, .. } => Some(err),
JobFailure { .. } => None,
}
}
}
#[derive(Debug)]
pub enum PackagesError {
NodeConfigLoadError { err: brane_cfg::info::YamlError },
UnsupportedNode { what: &'static str, kind: NodeKind },
FileNotAFile { path: PathBuf },
IllegalNameVersionPair { raw: String, err: specifications::version::ParseError },
DirReadError { what: &'static str, path: PathBuf, err: std::io::Error },
DirEntryReadError { what: &'static str, entry: usize, path: PathBuf, err: std::io::Error },
UnknownImage { path: PathBuf, name: String, version: Version },
HashError { err: brane_tsk::docker::Error },
}
impl Display for PackagesError {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use PackagesError::*;
match self {
NodeConfigLoadError { err } => write!(f, "Failed to load node.yml file: {err}"),
UnsupportedNode { what, kind } => write!(f, "Cannot {what} on a {} node", kind.variant()),
FileNotAFile { path } => write!(f, "Given image path '{}' exists but is not a file", path.display()),
IllegalNameVersionPair { raw, err } => write!(f, "Failed to parse given image name[:version] pair '{raw}': {err}"),
DirReadError { what, path, err } => write!(f, "Failed to read {} directory '{}': {}", what, path.display(), err),
DirEntryReadError { what, entry, path, err } => {
write!(f, "Failed to read entry {} in {} directory '{}': {}", entry, what, path.display(), err)
},
UnknownImage { path, name, version } => write!(f, "No image for package '{}', version {} found in '{}'", name, version, path.display()),
HashError { err } => write!(f, "Failed to hash image: {err}"),
}
}
}
impl Error for PackagesError {}
#[derive(Debug)]
pub enum UnpackError {
NodeConfigError { err: brane_cfg::info::YamlError },
FileWriteError { what: &'static str, path: PathBuf, err: std::io::Error },
TargetDirCreateError { path: PathBuf, err: std::io::Error },
TargetDirNotFound { path: PathBuf },
TargetDirNotADir { path: PathBuf },
}
impl Display for UnpackError {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use UnpackError::*;
match self {
NodeConfigError { err } => write!(f, "Failed to read node config file: {err} (specify a kind manually using '--kind')"),
FileWriteError { what, path, err } => write!(f, "Failed to write {} file to '{}': {}", what, path.display(), err),
TargetDirCreateError { path, err } => write!(f, "Failed to create target directory '{}': {}", path.display(), err),
TargetDirNotFound { path } => {
write!(f, "Target directory '{}' not found (you can create it by re-running this command with '-f')", path.display())
},
TargetDirNotADir { path } => write!(f, "Target directory '{}' exists but is not a directory", path.display()),
}
}
}
impl Error for UnpackError {}
#[derive(Debug)]
pub enum DockerClientVersionParseError {
MissingDot { raw: String },
IllegalMajorNumber { raw: String, err: std::num::ParseIntError },
IllegalMinorNumber { raw: String, err: std::num::ParseIntError },
}
impl Display for DockerClientVersionParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use DockerClientVersionParseError::*;
match self {
MissingDot { raw } => write!(f, "Missing '.' in Docket client version number '{raw}'"),
IllegalMajorNumber { raw, err } => write!(f, "'{raw}' is not a valid Docket client version major number: {err}"),
IllegalMinorNumber { raw, err } => write!(f, "'{raw}' is not a valid Docket client version minor number: {err}"),
}
}
}
impl Error for DockerClientVersionParseError {}
#[derive(Debug)]
pub enum InclusiveRangeParseError {
MissingDash { raw: String },
NumberParseError { what: &'static str, raw: String, err: Box<dyn Send + Sync + Error> },
StartLargerThanEnd { start: String, end: String },
}
impl Display for InclusiveRangeParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use InclusiveRangeParseError::*;
match self {
MissingDash { raw } => write!(f, "Missing '-' in range '{raw}'"),
NumberParseError { what, raw, err } => write!(f, "Failed to parse '{raw}' as a valid {what}: {err}"),
StartLargerThanEnd { start, end } => write!(f, "Start index '{start}' is larger than end index '{end}'"),
}
}
}
impl Error for InclusiveRangeParseError {}
#[derive(Debug)]
pub enum PairParseError {
MissingSeparator { separator: char, raw: String },
IllegalSomething { what: &'static str, raw: String, err: Box<dyn Send + Sync + Error> },
}
impl Display for PairParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use PairParseError::*;
match self {
MissingSeparator { separator, raw } => write!(f, "Missing '{separator}' in location pair '{raw}'"),
IllegalSomething { what, raw, err } => write!(f, "Failed to parse '{raw}' as a {what}: {err}"),
}
}
}
impl Error for PairParseError {}
#[derive(Debug)]
pub enum PolicyInputLanguageParseError {
Unknown { raw: String },
}
impl Display for PolicyInputLanguageParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use PolicyInputLanguageParseError::*;
match self {
Unknown { raw } => write!(f, "Unknown policy input language '{raw}' (options are 'eflint' or 'eflint-json')"),
}
}
}
impl Error for PolicyInputLanguageParseError {}
#[derive(Debug)]
pub enum ArchParseError {
SpawnError { command: Command, err: std::io::Error },
SpawnFailure { command: Command, status: ExitStatus, err: String },
UnknownArch { raw: String },
}
impl Display for ArchParseError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use ArchParseError::*;
match self {
SpawnError { command, err } => write!(f, "Failed to run '{command:?}': {err}"),
SpawnFailure { command, status, err } => {
write!(f, "Command '{:?}' failed with exit code {}\n\nstderr:\n{}\n\n", command, status.code().unwrap_or(-1), err)
},
UnknownArch { raw } => write!(f, "Unknown architecture '{raw}'"),
}
}
}
impl Error for ArchParseError {}
#[derive(Debug)]
pub enum JwtAlgorithmParseError {
Unknown { raw: String },
}
impl Display for JwtAlgorithmParseError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use JwtAlgorithmParseError::*;
match self {
Unknown { raw } => write!(f, "Unknown JWT algorithm '{raw}' (options are: 'HS256')"),
}
}
}
impl Error for JwtAlgorithmParseError {}
#[derive(Debug)]
pub enum KeyTypeParseError {
Unknown { raw: String },
}
impl Display for KeyTypeParseError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use KeyTypeParseError::*;
match self {
Unknown { raw } => write!(f, "Unknown key type '{raw}' (options are: 'oct')"),
}
}
}
impl Error for KeyTypeParseError {}
#[derive(Debug)]
pub enum KeyUsageParseError {
Unknown { raw: String },
}
impl Display for KeyUsageParseError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use KeyUsageParseError::*;
match self {
Unknown { raw } => write!(f, "Unknown key usage '{raw}' (options are: 'sig')"),
}
}
}
impl Error for KeyUsageParseError {}