use std::fmt::{Display, Formatter, Result as FResult};
use std::ops::RangeInclusive;
use std::path::PathBuf;
use std::str::FromStr;
use brane_cfg::node::NodeKind;
use brane_tsk::docker::{ClientVersion, ImageSource};
use clap::Subcommand;
use enum_debug::EnumDebug;
use specifications::address::Address;
use specifications::version::Version;
use crate::errors::{InclusiveRangeParseError, PairParseError, PolicyInputLanguageParseError};
lazy_static::lazy_static! {
pub static ref API_DEFAULT_VERSION: String = format!("{}", brane_tsk::docker::API_DEFAULT_VERSION);
}
#[derive(Clone, Copy, Debug)]
pub struct ResolvableNodeKind(pub Option<NodeKind>);
impl Display for ResolvableNodeKind {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
match self.0 {
Some(kind) => write!(f, "{kind}"),
None => write!(f, "$NODECFG"),
}
}
}
impl FromStr for ResolvableNodeKind {
type Err = brane_cfg::errors::NodeKindParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"$NODECFG" => Ok(Self(None)),
raw => Ok(Self(Some(NodeKind::from_str(raw)?))),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VersionFix(pub Option<Version>);
impl Display for VersionFix {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "{}", if let Some(version) = self.0 { version.to_string() } else { "all".into() }) }
}
impl FromStr for VersionFix {
type Err = specifications::version::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "all" {
return Ok(Self(None));
}
Ok(Self(Some(Version::from_str(s)?)))
}
}
#[derive(Clone, Debug)]
pub struct InclusiveRange<T>(pub RangeInclusive<T>);
impl<T> InclusiveRange<T> {
#[inline]
#[track_caller]
pub fn new(start: T, end: T) -> Self
where
T: Display + PartialOrd,
{
if start > end {
panic!("`start` cannot be later than `end` ({start} > {end})");
}
Self(RangeInclusive::new(start, end))
}
}
impl<T: Display> Display for InclusiveRange<T> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "{}-{}", self.0.start(), self.0.end()) }
}
impl<T: FromStr + PartialOrd> FromStr for InclusiveRange<T>
where
T::Err: 'static + Send + Sync + std::error::Error,
{
type Err = InclusiveRangeParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let dpos: usize = match s.find('-') {
Some(pos) => pos,
None => {
return Err(InclusiveRangeParseError::MissingDash { raw: s.into() });
},
};
let sstart: &str = &s[..dpos];
let send: &str = &s[dpos + 1..];
let start: T = T::from_str(sstart).map_err(|err| InclusiveRangeParseError::NumberParseError {
what: std::any::type_name::<T>(),
raw: sstart.into(),
err: Box::new(err),
})?;
let end: T = T::from_str(send).map_err(|err| InclusiveRangeParseError::NumberParseError {
what: std::any::type_name::<T>(),
raw: send.into(),
err: Box::new(err),
})?;
if start > end {
return Err(InclusiveRangeParseError::StartLargerThanEnd { start: sstart.into(), end: send.into() });
}
Ok(Self(start..=end))
}
}
#[derive(Clone, Debug)]
pub struct Pair<K, const C: char, V>(pub K, pub V);
impl<K: Display, const C: char, V: Display> Display for Pair<K, C, V> {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "{}: {}", self.0, self.1) }
}
impl<K: FromStr, const C: char, V: FromStr> FromStr for Pair<K, C, V>
where
K::Err: 'static + Send + Sync + std::error::Error,
V::Err: 'static + Send + Sync + std::error::Error,
{
type Err = PairParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let sep_pos: usize = match s.find(C) {
Some(pos) => pos,
None => {
return Err(PairParseError::MissingSeparator { separator: C, raw: s.into() });
},
};
let skey: &str = &s[..sep_pos];
let svalue: &str = &s[sep_pos + 1..];
let key: K = K::from_str(skey).map_err(|err| PairParseError::IllegalSomething {
what: std::any::type_name::<K>(),
raw: skey.into(),
err: Box::new(err),
})?;
let value: V = V::from_str(svalue).map_err(|err| PairParseError::IllegalSomething {
what: std::any::type_name::<V>(),
raw: svalue.into(),
err: Box::new(err),
})?;
Ok(Self(key, value))
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum PolicyInputLanguage {
EFlint,
EFlintJson,
}
impl Display for PolicyInputLanguage {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use PolicyInputLanguage::*;
match self {
EFlint => write!(f, "eFLINT"),
EFlintJson => write!(f, "eFLINT JSON"),
}
}
}
impl FromStr for PolicyInputLanguage {
type Err = PolicyInputLanguageParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"eflint" => Ok(Self::EFlint),
"eflint-json" => Ok(Self::EFlintJson),
raw => Err(PolicyInputLanguageParseError::Unknown { raw: raw.into() }),
}
}
}
#[derive(Clone, Debug)]
pub struct StartOpts {
pub compose_verbose: bool,
pub version: Version,
pub image_dir: PathBuf,
pub local_aux: bool,
pub skip_import: bool,
pub profile_dir: Option<PathBuf>,
}
#[derive(Clone, Debug)]
pub struct LogsOpts {
pub compose_verbose: bool,
}
#[derive(Debug, EnumDebug, Subcommand)]
pub enum DownloadServicesSubcommand {
#[clap(name = "central", about = "Downloads the central node services (brane-api, brane-drv, brane-plr, brane-prx)")]
Central,
#[clap(name = "worker", about = "Downloads the worker node services (brane-reg, brane-job, brane-prx)")]
Worker,
#[clap(
name = "auxillary",
about = "Downloads the auxillary services for the central node. Note that most of these are actually downloaded using Docker."
)]
Auxillary {
#[clap(short, long, default_value = "/var/run/docker.sock", help = "The path of the Docker socket to connect to.")]
socket: PathBuf,
#[clap(short, long, default_value=API_DEFAULT_VERSION.as_str(), help="The client version to connect to the Docker instance with.")]
client_version: ClientVersion,
},
}
#[derive(Debug, EnumDebug, Subcommand)]
#[allow(clippy::large_enum_variant)] pub enum GenerateNodeSubcommand {
#[clap(name = "central", about = "Generates a node.yml file for a central node with default values.")]
Central {
#[clap(name = "HOSTNAME", help = "The hostname that other nodes in the instance can use to reach this node.")]
hostname: String,
#[clap(
short,
long,
default_value = "$CONFIG/infra.yml",
help = "The location of the 'infra.yml' file. Use '$CONFIG' to reference the value given by '--config-path'."
)]
infra: PathBuf,
#[clap(
short = 'P',
long,
default_value = "$CONFIG/proxy.yml",
help = "The location of the 'proxy.yml' file. Use '$CONFIG' to reference the value given by '--config-path'."
)]
proxy: PathBuf,
#[clap(
short,
long,
default_value = "$CONFIG/certs",
help = "The location of the certificate directory. Use '$CONFIG' to reference the value given by '--config-path'."
)]
certs: PathBuf,
#[clap(long, default_value = "./packages", help = "The location of the package directory.")]
packages: PathBuf,
#[clap(long, conflicts_with_all = [ "prx_name", "prx_port" ], help = "If given, will use a proxy service running on the external address instead of one in this Docker service. This will mean that it will _not_ be spawned when running 'branectl start'.")]
external_proxy: Option<Address>,
#[clap(long, default_value = "brane-api", help = "The name of the API service's container.")]
api_name: String,
#[clap(long, default_value = "brane-drv", help = "The name of the driver service's container.")]
drv_name: String,
#[clap(long, default_value = "brane-plr", help = "The name of the planner service's container.")]
plr_name: String,
#[clap(long, default_value = "brane-prx", help = "The name of the proxy service's container.")]
prx_name: String,
#[clap(long, default_value = "50051", help = "The port on which the API service is available.")]
api_port: u16,
#[clap(long, default_value = "50052", help = "The port on which the planner service is available.")]
plr_port: u16,
#[clap(long, default_value = "50053", help = "The port on which the driver service is available.")]
drv_port: u16,
#[clap(long, default_value = "50050", help = "The port on which the proxy service is available.")]
prx_port: u16,
},
#[clap(name = "worker", about = "Generate a node.yml file for a worker node with default values.")]
Worker {
#[clap(name = "HOSTNAME", help = "The hostname that other nodes in the instance can use to reach this node.")]
hostname: String,
#[clap(name = "LOCATION_ID", help = "The location identifier (location ID) of this node.")]
location_id: String,
#[clap(long, help = "A list of use-case registries to take into account.")]
use_cases: Vec<Pair<String, '=', Address>>,
#[clap(
long,
default_value = "$CONFIG/backend.yml",
help = "The location of the `backend.yml` file. Use `$CONFIG` to reference the value given by --config-path. "
)]
backend: PathBuf,
#[clap(
long,
default_value = "./policies.db",
help = "The location of the `policies.db` file that determines which containers and users are allowed to be executed."
)]
policy_database: PathBuf,
#[clap(
long,
default_value = "$CONFIG/policy_deliberation_secret.json",
help = "The location of the `policy_deliberation_secret.json` file that is used to verify authentication on the deliberation endpoint \
in the checker. Use '$CONFIG' to reference the value given by --config-path."
)]
policy_deliberation_secret: PathBuf,
#[clap(
long,
default_value = "$CONFIG/policy_expert_secret.json",
help = "The location of the `policy_expert_secret.json` file that is used to verify authentication on the policy expert endpoint in the \
checker. Use '$CONFIG' to reference the value given by --config-path."
)]
policy_expert_secret: PathBuf,
#[clap(
long,
help = "If given, will map the audit log of the checker to some persistent location. Use '$CONFIG' to reference the value given by \
--config-path."
)]
policy_audit_log: Option<PathBuf>,
#[clap(
short = 'P',
long,
default_value = "$CONFIG/proxy.yml",
help = "The location of the 'proxy.yml' file. Use '$CONFIG' to reference the value given by '--config-path'."
)]
proxy: PathBuf,
#[clap(
short,
long,
default_value = "$CONFIG/certs",
help = "The location of the certificate directory. Use '$CONFIG' to reference the value given by --config-path."
)]
certs: PathBuf,
#[clap(long, default_value = "./packages", help = "The location of the package directory.")]
packages: PathBuf,
#[clap(short, long, default_value = "./data", help = "The location of the data directory.")]
data: PathBuf,
#[clap(short, long, default_value = "./results", help = "The location of the results directory.")]
results: PathBuf,
#[clap(short = 'D', long, default_value = "/tmp/data", help = "The location of the temporary/downloaded data directory.")]
temp_data: PathBuf,
#[clap(short = 'R', long, default_value = "/tmp/results", help = "The location of the temporary/download results directory.")]
temp_results: PathBuf,
#[clap(long, conflicts_with_all = [ "prx_name", "prx_port" ], help = "If given, will use a proxy service running on the external address instead of one in this Docker service. This will mean that it will _not_ be spawned when running 'branectl start'.")]
external_proxy: Option<Address>,
#[clap(
long,
default_value = "brane-reg-$LOCATION",
help = "The name of the local registry service's container. Use '$LOCATION' to use the location ID."
)]
reg_name: String,
#[clap(
long,
default_value = "brane-job-$LOCATION",
help = "The name of the local delegate service's container. Use '$LOCATION' to use the location ID."
)]
job_name: String,
#[clap(
long,
default_value = "brane-chk-$LOCATION",
help = "The name of the local checker service's container. Use '$LOCATION' to use the location ID."
)]
chk_name: String,
#[clap(
long,
default_value = "brane-prx-$LOCATION",
help = "The name of the local proxy service's container. Use '$LOCATION' to use the location ID."
)]
prx_name: String,
#[clap(long, default_value = "50051", help = "The port on which the local registry service is available.")]
reg_port: u16,
#[clap(long, default_value = "50052", help = "The port on which the local delegate service is available.")]
job_port: u16,
#[clap(long, default_value = "50053", help = "The port on which the local checker service is available.")]
chk_port: u16,
#[clap(short, long, default_value = "50050", help = "The port on which the local proxy service is available.")]
prx_port: u16,
},
#[clap(name = "proxy", about = "Generate a node.yml file for a proxy node with default values.")]
Proxy {
#[clap(name = "HOSTNAME", help = "The hostname that other nodes in the instance can use to reach this node.")]
hostname: String,
#[clap(
short = 'P',
long,
default_value = "$CONFIG/proxy.yml",
help = "The location of the 'proxy.yml' file. Use '$CONFIG' to reference the value given by '--config-path'."
)]
proxy: PathBuf,
#[clap(
short,
long,
default_value = "$CONFIG/certs",
help = "The location of the certificate directory. Use '$CONFIG' to reference the value given by --config-path."
)]
certs: PathBuf,
#[clap(long, default_value = "brane-prx", help = "The name of the local proxy service's container.")]
prx_name: String,
#[clap(short, long, default_value = "50050", help = "The port on which the local proxy service is available.")]
prx_port: u16,
},
}
#[derive(Debug, EnumDebug, Subcommand)]
pub enum GenerateCertsSubcommand {
Server {
#[clap(name = "LOCATION_ID", help = "The name of the location for which we are generating server certificates.")]
location_id: String,
#[clap(
short = 'H',
long,
default_value = "$LOCATION_ID",
help = "The hostname of the location for which we are generating server certificates. Can use '$LOCATION_ID' to use the same value as \
given for the location ID."
)]
hostname: String,
},
Client {
#[clap(
name = "LOCATION_ID",
help = "The name of the location for which we are generating server certificates. Note that this the location ID of the client, not the \
server."
)]
location_id: String,
#[clap(
short = 'H',
long,
default_value = "$LOCATION_ID",
help = "The hostname of the location for which we are generating server certificates. Note that this the hostname of the client, not \
the server. Can use '$LOCATION_ID' to use the same value as given for the location ID."
)]
hostname: String,
#[clap(
short,
long,
default_value = "./ca.pem",
help = "The path to the certificate authority's certificate file that we will use to sign the client certificate."
)]
ca_cert: PathBuf,
#[clap(
short = 'k',
long,
default_value = "./ca-key.pem",
help = "The path to the certificate authority's private key file that we will use to sign the client certificate."
)]
ca_key: PathBuf,
},
}
impl GenerateCertsSubcommand {
#[inline]
pub fn resolve_hostname(&mut self) {
use GenerateCertsSubcommand::*;
match self {
Server { location_id, hostname, .. } | Client { location_id, hostname, .. } => {
if hostname == "$LOCATION_ID" {
hostname.clone_from(location_id);
}
},
}
}
#[inline]
pub fn location_id(&self) -> &str {
use GenerateCertsSubcommand::*;
match self {
Server { location_id, .. } | Client { location_id, .. } => location_id,
}
}
#[inline]
pub fn hostname(&self) -> &str {
use GenerateCertsSubcommand::*;
match self {
Server { hostname, .. } | Client { hostname, .. } => hostname,
}
}
}
#[derive(Debug, EnumDebug, Subcommand)]
pub enum GenerateBackendSubcommand {
#[clap(name = "local", about = "Generate a backend.yml for a local backend.")]
Local {
#[clap(
short,
long,
default_value = "/var/run/docker.sock",
help = "The location of the Docker socket that the delegate service should connect to."
)]
socket: PathBuf,
#[clap(short, long, help = "If given, fixes the Docker client version to the given one.")]
client_version: Option<ClientVersion>,
},
}
#[derive(Debug, Subcommand)]
pub enum StartSubcommand {
#[clap(name = "central", about = "Starts a central node based on the values in the local node.yml file.")]
Central {
#[clap(
short = 's',
long,
help = "The image to load for the aux-scylla service. If it's a path that exists, will attempt to load that file; otherwise, assumes \
it's an image name in a remote registry. You can wrap your names in either `Path<...>` or `Registry<...>` if it matters. \
Default: 'Registry<scylladb/scylla:4.6.3>', unless '--local-aux' is given. In that case, 'Path<$IMG_DIR/aux-scylla.tar>' is \
used as default instead."
)]
aux_scylla: Option<ImageSource>,
#[clap(
short = 'P',
long,
default_value = "Path<$IMG_DIR/brane-prx.tar>",
help = "The image to load for the brane-prx service. If it's a path that exists, will attempt to load that file; otherwise, assumes \
it's an image name in a remote registry. You can wrap your names in either `Path<...>` or `Registry<...>` if it matters. \
Finally, use '$IMG_DIR' to reference the value indicated by '--image-dir'."
)]
brane_prx: ImageSource,
#[clap(
short = 'a',
long,
default_value = "Path<$IMG_DIR/brane-api.tar>",
help = "The image to load for the brane-plr service. If it's a path that exists, will attempt to load that file; otherwise, assumes \
it's an image name in a remote registry. You can wrap your names in either `Path<...>` or `Registry<...>` if it matters. \
Finally, use '$IMG_DIR' to reference the value indicated by '--image-dir'."
)]
brane_api: ImageSource,
#[clap(
short = 'd',
long,
default_value = "Path<$IMG_DIR/brane-drv.tar>",
help = "The image to load for the brane-drv service. If it's a path that exists, will attempt to load that file; otherwise, assumes \
it's an image name in a remote registry. You can wrap your names in either `Path<...>` or `Registry<...>` if it matters. \
Finally, use '$IMG_DIR' to reference the value indicated by '--image-dir'."
)]
brane_drv: ImageSource,
#[clap(
short = 'p',
long,
default_value = "Path<$IMG_DIR/brane-plr.tar>",
help = "The image to load for the brane-plr service. If it's a path that exists, will attempt to load that file; otherwise, assumes \
it's an image name in a remote registry. You can wrap your names in either `Path<...>` or `Registry<...>` if it matters. \
Finally, use '$IMG_DIR' to reference the value indicated by '--image-dir'."
)]
brane_plr: ImageSource,
},
#[clap(name = "worker", about = "Starts a worker node based on the values in the local node.yml file.")]
Worker {
#[clap(
short = 'P',
long,
default_value = "Path<$IMG_DIR/brane-prx.tar>",
help = "The image to load for the brane-prx service. If it's a path that exists, will attempt to load that file; otherwise, assumes \
it's an image name in a remote registry. You can wrap your names in either `Path<...>` or `Registry<...>` if it matters. \
Finally, use '$IMG_DIR' to reference the value indicated by '--image-dir'."
)]
brane_prx: ImageSource,
#[clap(
short = 'c',
long,
default_value = "Path<$IMG_DIR/brane-chk.tar>",
help = "The image to load for the brane-chk service. If it's a path that exists, will attempt to load that file; otherwise, assumes \
it's an image name in a remote registry. You can wrap your names in either `Path<...>` or `Registry<...>` if it matters. \
Finally, use '$IMG_DIR' to reference the value indicated by '--image-dir'."
)]
brane_chk: ImageSource,
#[clap(
short = 'r',
long,
default_value = "Path<$IMG_DIR/brane-reg.tar>",
help = "The image to load for the brane-reg service. If it's a path that exists, will attempt to load that file; otherwise, assumes \
it's an image name in a remote registry. You can wrap your names in either `Path<...>` or `Registry<...>` if it matters. \
Finally, use '$IMG_DIR' to reference the value indicated by '--image-dir'."
)]
brane_reg: ImageSource,
#[clap(
short = 'j',
long,
default_value = "Path<$IMG_DIR/brane-job.tar>",
help = "The image to load for the brane-job service. If it's a path that exists, will attempt to load that file; otherwise, assumes \
it's an image name in a remote registry. You can wrap your names in either `Path<...>` or `Registry<...>` if it matters. \
Finally, use '$IMG_DIR' to reference the value indicated by '--image-dir'."
)]
brane_job: ImageSource,
},
#[clap(name = "proxy", about = "Starts a proxy node based on the values in the local node.yml file.")]
Proxy {
#[clap(
short = 'P',
long,
default_value = "Path<$IMG_DIR/brane-prx.tar>",
help = "The image to load for the brane-prx service. If it's a path that exists, will attempt to load that file; otherwise, assumes \
it's an image name in a remote registry. You can wrap your names in either `Path<...>` or `Registry<...>` if it matters. \
Finally, use '$IMG_DIR' to reference the value indicated by '--image-dir'."
)]
brane_prx: ImageSource,
},
}