Compare commits

...

10 Commits

Author SHA1 Message Date
Nadja von Reitzenstein Čerpnjak
cd644ea853 Merge branch 'feature/adr-backfill' into 'development'
Draft: ADR backfill

Closes #42

See merge request fabinfra/fabaccess/bffh!26
2024-12-14 21:04:12 +00:00
57a0436ca1 fix typo; fixes https://gitlab.com/fabinfra/fabaccess/bffh/-/issues/68 2024-12-14 21:54:01 +01:00
Jonathan Krebs
8b15acf983 remove warnings around initiator loading. cleaner error handling remains todo. 2024-12-13 15:32:21 +01:00
Jonathan Krebs
40ba114e61 fix warnings: at the moment configuration by environment variables is not implemented 2024-12-13 15:32:21 +01:00
Jonathan Krebs
c2c34ede67 fix warnings: remove unused muts and variables 2024-12-13 15:32:21 +01:00
Jonathan Krebs
2b0fe0e868 add some error handling, mostly to quiet warnings 2024-12-13 15:32:21 +01:00
Jonathan Krebs
fbfb76c34e fix warnings: some more easy cases 2024-12-13 15:32:21 +01:00
Jonathan Krebs
971dee36fd fix warnings: replace some mem::replace with assignments 2024-12-13 15:32:21 +01:00
Jonathan Krebs
41983e6039 remove unused imports from bffhd 2024-12-13 15:32:21 +01:00
Nadja Reitzenstein
a62a5678dc ADR-0000 - Choosing a programming Language and Framework for BFFH 2022-05-31 12:14:55 +02:00
35 changed files with 290 additions and 113 deletions

View File

@ -56,7 +56,7 @@ But before you open an issue in this repo for a feature request, please first ch
## Contributing Code
To help develop Diflouroborane you will need a Rust toolchain. I heavily recommend installing
To help develop Difluoroborane you will need a Rust toolchain. I heavily recommend installing
[rustup](https://rustup.rs) even if your distribution provides a recent enough rustc, simply because
it allows to easily switch compilers between several versions of both stable and nightly. It also
allows you to download the respective stdlib crate, giving you the option of an offline reference.

View File

@ -1,10 +1,10 @@
# Installation
Currently there are no distribution packages available.
However installation is reasonably straight-forward, since Diflouroborane compiles into a single
However installation is reasonably straight-forward, since Difluoroborane compiles into a single
mostly static binary with few dependencies.
At the moment only Linux is supported. If you managed to compile Diflouroborane please open an issue
At the moment only Linux is supported. If you managed to compile Difluoroborane please open an issue
outlining your steps or add a merge request expanding this part. Thanks!
## Requirements
@ -12,7 +12,7 @@ outlining your steps or add a merge request expanding this part. Thanks!
General requirements; scroll down for distribution-specific instructions
- GNU SASL (libgsasl).
* If you want to compile Diflouroborane from source you will potentially also need development
* If you want to compile Difluoroborane from source you will potentially also need development
headers
- capnproto
- rustc stable / nightly >= 1.48
@ -26,7 +26,7 @@ $ pacman -S gsasl rust capnproto
## Compiling from source
Diflouroborane uses Cargo, so compilation boils down to:
Difluoroborane uses Cargo, so compilation boils down to:
```shell
$ cargo build --release

View File

@ -1,6 +1,6 @@
# FabAccess Diflouroborane
# FabAccess Difluoroborane
Diflouroborane (shorter: BFFH, the chemical formula for Diflouroborane) is the server part of
Difluoroborane (shorter: BFFH, the chemical formula for Difluoroborane) is the server part of
FabAccess.
It provides a server-side implementation of the [FabAccess API](https://gitlab.com/fabinfra/fabaccess/fabaccess-api).

View File

@ -12,7 +12,7 @@ use std::future::Future;
use std::pin::Pin;
use miette::{Diagnostic, IntoDiagnostic};
use miette::Diagnostic;
use std::task::{Context, Poll};
use std::time::Duration;
use thiserror::Error;

View File

@ -2,7 +2,6 @@ use desfire::desfire::desfire::MAX_BYTES_PER_TRANSACTION;
use desfire::desfire::Desfire;
use desfire::error::Error as DesfireError;
use desfire::iso7816_4::apduresponse::APDUResponse;
use rsasl::callback::SessionData;
use rsasl::mechanism::{
Authentication, Demand, DemandReply, MechanismData, MechanismError, MechanismErrorKind,
Provider, State, ThisProvider,
@ -13,7 +12,6 @@ use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fmt::{Debug, Display, Formatter};
use std::io::Write;
use std::sync::Arc;
use crate::authentication::fabfire::FabFireCardKey;
@ -104,6 +102,7 @@ struct AuthInfo {
iv: Vec<u8>,
}
#[allow(non_camel_case_types)]
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "Cmd")]
enum CardCommand {

View File

@ -2,7 +2,6 @@ use desfire::desfire::desfire::MAX_BYTES_PER_TRANSACTION;
use desfire::desfire::Desfire;
use desfire::error::Error as DesfireError;
use desfire::iso7816_4::apduresponse::APDUResponse;
use rsasl::callback::SessionData;
use rsasl::mechanism::{
Authentication, Demand, DemandReply, MechanismData, MechanismError, MechanismErrorKind,
Provider, State, ThisProvider,
@ -13,7 +12,6 @@ use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fmt::{Debug, Display, Formatter};
use std::io::Write;
use std::sync::Arc;
use crate::authentication::fabfire::FabFireCardKey;
use crate::CONFIG;

View File

@ -26,7 +26,7 @@ impl Callback {
impl SessionCallback for Callback {
fn callback(
&self,
session_data: &SessionData,
_session_data: &SessionData,
context: &Context,
request: &mut Request,
) -> Result<(), SessionError> {

View File

@ -4,10 +4,8 @@ use capnp_rpc::pry;
use rsasl::mechname::Mechname;
use rsasl::prelude::State as SaslState;
use rsasl::prelude::{MessageSent, Session};
use rsasl::property::AuthId;
use std::fmt;
use std::fmt::{Formatter, Write};
use std::io::Cursor;
use tracing::Span;
use crate::authentication::V;
@ -115,7 +113,7 @@ impl AuthenticationSystem for Authentication {
f.write_char(')')
}
}
let mut response;
let response;
let mut builder = results.get();
if let State::Running(mut session, manager) =

View File

@ -211,7 +211,6 @@ impl ManageServer for Machine {
mut result: manage::GetMachineInfoExtendedResults,
) -> Promise<(), ::capnp::Error> {
let mut builder = result.get();
let user = User::new_self(self.session.clone());
User::build_optional(
&self.session,
self.resource.get_current_user(),

View File

@ -5,11 +5,11 @@ use async_net::TcpListener;
use capnp_rpc::rpc_twoparty_capnp::Side;
use capnp_rpc::twoparty::VatNetwork;
use capnp_rpc::RpcSystem;
use executor::prelude::{Executor, GroupId, SupervisionRegistry};
use executor::prelude::{Executor, SupervisionRegistry};
use futures_rustls::server::TlsStream;
use futures_rustls::TlsAcceptor;
use futures_util::stream::FuturesUnordered;
use futures_util::{stream, AsyncRead, AsyncWrite, FutureExt, StreamExt};
use futures_util::{stream, AsyncRead, AsyncWrite, StreamExt};
use std::future::Future;
use std::io;

View File

@ -1,4 +1,3 @@
use crate::authorization::roles::Role;
use crate::Roles;
use api::permissionsystem_capnp::permission_system::info::{
GetRoleListParams, GetRoleListResults, Server as PermissionSystem,
@ -37,7 +36,7 @@ impl PermissionSystem for Permissions {
tracing::trace!("method call");
let roles = self.roles.list().collect::<Vec<&String>>();
let mut builder = results.get();
let builder = results.get();
let mut b = builder.init_role_list(roles.len() as u32);
for (i, role) in roles.into_iter().enumerate() {
let mut role_builder = b.reborrow().get(i as u32);

View File

@ -109,7 +109,7 @@ impl manage::Server for User {
if let Some(mut user) = self.session.users.get_user(uid) {
if let Ok(true) = user.check_password(old_pw.as_bytes()) {
user.set_pw(new_pw.as_bytes());
self.session.users.put_user(uid, &user);
pry!(self.session.users.put_user(uid, &user));
}
}
Promise::ok(())
@ -143,9 +143,9 @@ impl admin::Server for User {
// Only update if needed
if !target.userdata.roles.iter().any(|r| r.as_str() == rolename) {
target.userdata.roles.push(rolename.to_string());
self.session
pry!(self.session
.users
.put_user(self.user.get_username(), &target);
.put_user(self.user.get_username(), &target));
}
}
@ -168,9 +168,9 @@ impl admin::Server for User {
// Only update if needed
if target.userdata.roles.iter().any(|r| r.as_str() == rolename) {
target.userdata.roles.retain(|r| r.as_str() != rolename);
self.session
pry!(self.session
.users
.put_user(self.user.get_username(), &target);
.put_user(self.user.get_username(), &target));
}
}
@ -185,7 +185,7 @@ impl admin::Server for User {
let uid = self.user.get_username();
if let Some(mut user) = self.session.users.get_user(uid) {
user.set_pw(new_pw.as_bytes());
self.session.users.put_user(uid, &user);
pry!(self.session.users.put_user(uid, &user));
}
Promise::ok(())
}
@ -221,7 +221,7 @@ impl card_d_e_s_fire_e_v2::Server for User {
Vec::new()
});
if !tk.is_empty() {
let mut b = results.get();
let b = results.get();
let mut lb = b.init_token_list(1);
lb.set(0, &tk[..]);
}
@ -299,7 +299,8 @@ impl card_d_e_s_fire_e_v2::Server for User {
.insert("cardtoken".to_string(), token.to_string());
user.userdata.kv.insert("cardkey".to_string(), card_key);
self.session.users.put_user(self.user.get_username(), &user);
pry!(self.session.users.put_user(self.user.get_username(), &user));
Promise::ok(())
}
@ -338,7 +339,7 @@ impl card_d_e_s_fire_e_v2::Server for User {
}
}
self.session.users.put_user(self.user.get_username(), &user);
pry!(self.session.users.put_user(self.user.get_username(), &user));
Promise::ok(())
}

View File

@ -84,13 +84,13 @@ impl manage::Server for Users {
"method call"
);
let mut builder = result.get();
let builder = result.get();
if !username.is_empty() && !password.is_empty() {
if self.session.users.get_user(username).is_none() {
let user = db::User::new_with_plain_pw(username, password);
self.session.users.put_user(username, &user);
let mut builder = builder.init_successful();
pry!(self.session.users.put_user(username, &user));
let builder = builder.init_successful();
User::fill(&self.session, user, builder);
} else {
let mut builder = builder.init_failed();

View File

@ -1,8 +1,6 @@
use std::collections::HashMap;
use std::default::Default;
use std::error::Error;
use std::fmt::{Debug, Display};
use std::marker::PhantomData;
use std::fmt::Debug;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
@ -12,7 +10,6 @@ use crate::authorization::roles::Role;
use crate::capnp::{Listen, TlsListen};
use crate::logging::LogConfig;
use miette::IntoDiagnostic;
use std::path::Path;
#[derive(Debug)]

View File

@ -38,13 +38,15 @@ pub fn read(file: impl AsRef<Path>) -> Result<Config, ConfigError> {
if !path.is_file() {
return Err(ConfigError::NotAFile(path.to_string_lossy().to_string()));
}
let mut config = dhall::read_config_file(file)?;
for (envvar, value) in std::env::vars() {
match envvar.as_str() {
// Do things like this?
// "BFFH_LOG" => config.logging.filter = Some(value),
_ => {}
}
}
let config = dhall::read_config_file(file)?;
// TODO: configuration by environment variables?
// but rather in in a separate function
// for (envvar, value) in std::env::vars() {
// match envvar.as_str() {
// // Do things like this?
// // "BFFH_LOG" => config.logging.filter = Some(value),
// _ => {}
// }
// }
Ok(config)
}

View File

@ -1,10 +1,13 @@
use thiserror::Error;
// for converting a database error into a failed promise
use capnp;
mod raw;
use miette::{Diagnostic, LabeledSpan, Severity, SourceCode};
use miette::{Diagnostic, Severity};
pub use raw::RawDB;
use std::fmt::{Debug, Display, Formatter};
use std::fmt::{Debug, Display};
mod typed;
pub use typed::{Adapter, AlignedAdapter, ArchivedValue, DB};
@ -79,3 +82,9 @@ impl Diagnostic for Error {
None
}
}
impl From<Error> for capnp::Error {
fn from(dberr: Error) -> capnp::Error {
capnp::Error::failed(format!("database error: {}", dberr.to_string()))
}
}

View File

@ -1,4 +1,3 @@
use super::Result;
use lmdb::{DatabaseFlags, Environment, RwTransaction, Transaction, WriteFlags};
#[derive(Debug, Clone)]

View File

@ -1,4 +1,4 @@
use miette::{Diagnostic, LabeledSpan, Severity, SourceCode};
use miette::{Diagnostic, Severity};
use std::error;
use std::fmt::{Display, Formatter};
use std::io;

View File

@ -5,14 +5,11 @@ use super::Initiator;
use crate::initiators::InitiatorCallbacks;
use crate::resources::modules::fabaccess::Status;
use crate::session::SessionHandle;
use crate::users::UserRef;
use async_io::Timer;
use futures_util::future::BoxFuture;
use futures_util::ready;
use lmdb::Stat;
use std::collections::HashMap;
use std::future::Future;
use std::mem;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
@ -64,10 +61,7 @@ impl Future for Dummy {
match &mut self.state {
DummyState::Empty => {
tracing::trace!("Dummy initiator is empty, initializing…");
mem::replace(
&mut self.state,
DummyState::Sleeping(Self::timer(), Some(Status::Free)),
);
self.state = DummyState::Sleeping(Self::timer(), Some(Status::Free));
}
DummyState::Sleeping(timer, next) => {
tracing::trace!("Sleep timer exists, polling it.");
@ -78,7 +72,7 @@ impl Future for Dummy {
let status = next.take().unwrap();
let f = self.flip(status);
mem::replace(&mut self.state, DummyState::Updating(f));
self.state = DummyState::Updating(f);
}
DummyState::Updating(f) => {
tracing::trace!("Update future exists, polling it .");
@ -87,10 +81,7 @@ impl Future for Dummy {
tracing::trace!("Update future completed, sleeping!");
mem::replace(
&mut self.state,
DummyState::Sleeping(Self::timer(), Some(next)),
);
self.state = DummyState::Sleeping(Self::timer(), Some(next));
}
}
}

View File

@ -3,22 +3,15 @@ use crate::initiators::process::Process;
use crate::resources::modules::fabaccess::Status;
use crate::session::SessionHandle;
use crate::{
AuthenticationHandle, Config, MachineState, Resource, ResourcesHandle, SessionManager,
AuthenticationHandle, Config, Resource, ResourcesHandle, SessionManager,
};
use async_compat::CompatExt;
use executor::prelude::Executor;
use futures_util::ready;
use miette::IntoDiagnostic;
use rumqttc::ConnectReturnCode::Success;
use rumqttc::{AsyncClient, ConnectionError, Event, Incoming, MqttOptions};
use std::collections::HashMap;
use std::fmt::Display;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use tracing::Span;
use url::Url;
mod dummy;
mod process;
@ -107,7 +100,7 @@ pub fn load(
config: &Config,
resources: ResourcesHandle,
sessions: SessionManager,
authentication: AuthenticationHandle,
_authentication: AuthenticationHandle,
) -> miette::Result<()> {
let span = tracing::info_span!("loading initiators");
let _guard = span.enter();

View File

@ -1,10 +1,9 @@
use super::Initiator;
use super::InitiatorCallbacks;
use crate::resources::modules::fabaccess::Status;
use crate::resources::state::State;
use crate::utils::linebuffer::LineBuffer;
use async_process::{Child, ChildStderr, ChildStdout, Command, Stdio};
use futures_lite::{ready, AsyncRead};
use futures_lite::AsyncRead;
use miette::{miette, IntoDiagnostic};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@ -117,7 +116,7 @@ impl ProcessState {
impl Future for Process {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Process {
state: Some(state),
buffer,

View File

@ -3,13 +3,12 @@
//#![warn(missing_docs)]
//#![warn(missing_crate_level_docs)]
//! Diflouroborane
//! Difluoroborane
//!
//! This is the capnp component of the FabAccess project.
//! The entry point of bffhd can be found in [bin/bffhd/main.rs](../bin/bffhd/main.rs)
use miette::Diagnostic;
use std::io;
use thiserror::Error;
pub mod config;
@ -48,7 +47,6 @@ mod tls;
use std::sync::Arc;
use futures_util::{FutureExt, StreamExt};
use miette::{Context, IntoDiagnostic, Report};
use once_cell::sync::OnceCell;
use crate::audit::AuditLog;
@ -69,7 +67,7 @@ use lightproc::recoverable_handle::RecoverableHandle;
use signal_hook::consts::signal::*;
use tracing::Span;
pub struct Diflouroborane {
pub struct Difluoroborane {
config: Config,
executor: Executor<'static>,
pub statedb: StateDB,
@ -89,6 +87,7 @@ impl error::Description for SignalHandlerErr {
}
#[derive(Debug, Error, Diagnostic)]
// TODO 0.5: #[non_exhaustive]
pub enum BFFHError {
#[error("DB operation failed")]
DBError(
@ -136,7 +135,7 @@ pub enum BFFHError {
),
}
impl Diflouroborane {
impl Difluoroborane {
pub fn setup() {}
pub fn new(config: Config) -> Result<Self, BFFHError> {
@ -212,7 +211,9 @@ impl Diflouroborane {
self.resources.clone(),
sessionmanager.clone(),
authentication.clone(),
);
).expect("initializing initiators failed");
// TODO 0.5: error handling. Add variant to BFFHError
actors::load(self.executor.clone(), &self.config, self.resources.clone())?;
let tlsconfig = TlsConfig::new(self.config.tlskeylog.as_ref(), !self.config.is_quiet())?;
@ -231,13 +232,13 @@ impl Diflouroborane {
self.executor.spawn(apiserver.handle_until(rx));
let f = async {
let mut sig = None;
let mut sig;
while {
sig = signals.next().await;
sig.is_none()
} {}
tracing::info!(signal = %sig.unwrap(), "Received signal");
tx.send(());
_ = tx.send(()); // ignore result, as an Err means that the executor we want to stop has already stopped
};
self.executor.run(f);

View File

@ -2,8 +2,7 @@ use serde::{Deserialize, Serialize};
use std::path::Path;
use tracing_subscriber::fmt::format::Format;
use tracing_subscriber::prelude::*;
use tracing_subscriber::reload::Handle;
use tracing_subscriber::{reload, EnvFilter};
use tracing_subscriber::EnvFilter;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogConfig {

View File

@ -85,10 +85,13 @@ impl Inner {
self.db.put(&self.id.as_bytes(), &state).unwrap();
tracing::trace!("Updated DB, sending update signal");
AUDIT
let res = AUDIT
.get()
.unwrap()
.log(self.id.as_str(), &format!("{}", state));
if let Err(e) = res {
tracing::error!("Writing to the audit log failed for {} {}: {e}", self.id.as_str(), state);
}
self.signal.set(state);
tracing::trace!("Sent update signal");
@ -161,7 +164,7 @@ impl Resource {
fn set_state(&self, state: MachineState) {
let mut serializer = AllocSerializer::<1024>::default();
serializer.serialize_value(&state);
serializer.serialize_value(&state).expect("serializing a MachineState shoud be infallible");
let archived = ArchivedValue::new(serializer.into_serializer().into_inner());
self.inner.set_state(archived)
}

View File

@ -3,7 +3,6 @@ use crate::utils::oid::ObjectIdentifier;
use once_cell::sync::Lazy;
use rkyv::{Archive, Archived, Deserialize, Infallible};
use std::fmt;
use std::fmt::Write;
use std::str::FromStr;
//use crate::oidvalue;

View File

@ -3,10 +3,8 @@ use thiserror::Error;
use crate::db;
use crate::db::{AlignedAdapter, ArchivedValue, RawDB, DB};
use lmdb::{DatabaseFlags, Environment, EnvironmentFlags, Transaction, WriteFlags};
use miette::{Diagnostic, LabeledSpan, Severity, SourceCode};
use std::any::TypeId;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use miette::Diagnostic;
use std::fmt::Debug;
use std::{path::Path, sync::Arc};
use crate::resources::state::State;
@ -54,8 +52,8 @@ impl StateDB {
}
pub fn open_with_env(env: Arc<Environment>) -> Result<Self, StateDBError> {
let db = unsafe { RawDB::open(&env, Some("state")) };
let db = db.map_err(|e| StateDBError::Open(e.into()))?;
let db = RawDB::open(&env, Some("state"))
.map_err(|e| StateDBError::Open(e.into()))?;
Ok(Self::new(env, db))
}
@ -66,8 +64,8 @@ impl StateDB {
pub fn create_with_env(env: Arc<Environment>) -> Result<Self, StateDBError> {
let flags = DatabaseFlags::empty();
let db = unsafe { RawDB::create(&env, Some("state"), flags) };
let db = db.map_err(|e| StateDBError::Create(e.into()))?;
let db = RawDB::create(&env, Some("state"), flags)
.map_err(|e| StateDBError::Create(e.into()))?;
Ok(Self::new(env, db))
}

View File

@ -1,5 +1,5 @@
use std::fmt::{Debug, Display, Formatter};
use std::{fmt, hash::Hasher};
use std::fmt;
use std::ops::Deref;

View File

@ -14,8 +14,6 @@ use inventory;
use rkyv::ser::{ScratchSpace, Serializer};
use serde::ser::SerializeMap;
use std::collections::HashMap;
use std::ops::Deref;

View File

@ -2,7 +2,6 @@ use lmdb::{DatabaseFlags, Environment, RwTransaction, Transaction, WriteFlags};
use rkyv::Infallible;
use std::collections::HashMap;
use miette::{Context, IntoDiagnostic};
use std::sync::Arc;
use crate::db;
@ -183,8 +182,8 @@ impl UserDB {
}
pub fn clear_txn(&self, txn: &mut RwTransaction) -> Result<(), db::Error> {
self.db.clear(txn);
Ok(())
// TODO: why was the result ignored here?
self.db.clear(txn)
}
pub fn get_all(&self) -> Result<HashMap<String, UserData>, db::Error> {

View File

@ -7,8 +7,7 @@ use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::io::Write;
use clap::ArgMatches;
use miette::{Context, Diagnostic, IntoDiagnostic, SourceOffset, SourceSpan};
use miette::{Diagnostic, IntoDiagnostic, SourceSpan};
use std::path::Path;
use std::sync::Arc;

View File

@ -1,5 +1,5 @@
use clap::{Arg, Command, ValueHint};
use diflouroborane::{config, Diflouroborane};
use difluoroborane::{config, Difluoroborane};
use std::str::FromStr;
use std::{env, io, io::Write, path::PathBuf};
@ -15,12 +15,12 @@ fn main() -> miette::Result<()> {
FabAccess {apiver}\n\
\t[{build_kind} build built on {build_time}]\n\
\t {rustc_version}\n\t {cargo_version}",
version=diflouroborane::env::PKG_VERSION,
version=difluoroborane::env::PKG_VERSION,
apiver="0.3",
rustc_version=diflouroborane::env::RUST_VERSION,
cargo_version=diflouroborane::env::CARGO_VERSION,
build_time=diflouroborane::env::BUILD_TIME_3339,
build_kind=diflouroborane::env::BUILD_RUST_CHANNEL))
rustc_version=difluoroborane::env::RUST_VERSION,
cargo_version=difluoroborane::env::CARGO_VERSION,
build_time=difluoroborane::env::BUILD_TIME_3339,
build_kind=difluoroborane::env::BUILD_RUST_CHANNEL))
.about(clap::crate_description!())
.arg(Arg::new("config")
.help("Path to the config file to use")
@ -98,7 +98,7 @@ fn main() -> miette::Result<()> {
let configpath = matches
.value_of("config")
.unwrap_or("/etc/diflouroborane.dhall");
.unwrap_or("/etc/difluoroborane.dhall");
// Check for the --print-default option first because we don't need to do anything else in that
// case.
@ -140,7 +140,7 @@ fn main() -> miette::Result<()> {
if matches.is_present("dump") {
return Err(miette::miette!("DB Dumping is currently not implemented, except for the users db, using `--dump-users`"));
} else if matches.is_present("dump-users") {
let bffh = Diflouroborane::new(config)?;
let bffh = Difluoroborane::new(config)?;
let number = bffh.users.dump_file(
matches.value_of("dump-users").unwrap(),
@ -151,7 +151,7 @@ fn main() -> miette::Result<()> {
return Ok(());
} else if matches.is_present("load") {
let bffh = Diflouroborane::new(config)?;
let bffh = Difluoroborane::new(config)?;
bffh.users.load_file(matches.value_of("load").unwrap())?;
@ -179,7 +179,7 @@ fn main() -> miette::Result<()> {
}
config.logging.format = matches.value_of("log format").unwrap_or("full").to_string();
let mut bffh = Diflouroborane::new(config)?;
let mut bffh = Difluoroborane::new(config)?;
bffh.run()?;
}

View File

@ -1,4 +1,4 @@
fn main() {
// Extract build-time information using the `shadow-rs` crate
shadow_rs::new();
shadow_rs::new().unwrap();
}

View File

@ -0,0 +1,111 @@
# Choosing a programming Language and Framework for BFFH
* Status: accepted <!-- optional -->
* Date: 18.02.2020 (?) <!-- optional -->
Technical Story: Decision regarding programming language and framework to use in BFFH / the backend code of FabAccess <!-- optional -->
## Context and Problem Statement
Programming language Dicussions are the perfect [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) topic.
Regardless at some point a decision has to be made so that people can start writing code and being generally useful.
Since FabAccess started as a project with several potential developers and even more stakeholders interested/relying on
the success of the software this discussion was *extra* spicy.
The relevant discussions were had in the timeframe from about November of 2019 to March of 2020.
## Decision Drivers <!-- optional -->
* Available developers, both short-term and medium-term should the project become a successful staple of
german/european/terran makerspaces
* Barrier of entry for new / additional developers.
* Tooling support, esp. regarding non-developers (documentation, ease of self-compilation, …)
* Library support / ecosystem in the problem domain
* Existing projects that could be extended as to not invent the wheel again
* Language Features
## Considered Options
* Python
* TypeScript
* Rust
* Erlang
* Ruby
* Haskell
## Decision Outcome
Chosen option: "Rust", because the project ended up mostly being developed by FabInfra whos cost/benefit analisys skews
in favour of type safety over speed of development.
### Positive Consequences <!-- optional -->
* Type safety of Rust strong compared to all other options barring Haskell
* Single statically linked binary with very few dependencies makes deployment trivial
* Compiled software close to the metal allows running the server on low-powered hardware such as Raspberry Pis even for
medium-sized deployments.
### Negative Consequences <!-- optional -->
* Speed of development suffers short-term. Rust is a lower-level language than all other evaluated options and requires
more thought and developer time to implement high level features.
* Available developer pool is small and expensive. Rust is not a good first programming language and comparatively hard
to get into for developers if they don't have experience with C++. Developers are available to hire but salary
expectation of Rust developers is high compared to Python / Java.
* Small ecosystem / few libraries. Rust is a very young programming language and despite being rated as the most popular
language on SO for several years and being commercially used has fewer libraries than most other options. Support for
e.g. LDAP is not mature. But problem domain of FabAccess is in the area that Rust has the most support for.
## Pros and Cons of the Options <!-- optional -->
### Python
* Good, because large available developer pool
* Good, because of the developers interested in developing it large parts have experience with Python
* Good, because Python is easy to learn and a good first language
* Good, because Python has extensive library support and an ecosystem geared towards automation
* Bad, because comparatively slow. Slowest or second slowest option considered.
* Bad, because highly dynamic typing and mediocre static analysis has potential for edgecase bugs being hidden for long
times
* Bad, because Python gives little control over the crash process. Catching exceptions is very coarse.
### TypeScript
* Good, because stronger typing than Python and Ruby
* Bad, because interpreted language and while faster than Ruby/Python still much slower than the compiled options.
* Bad, because the Node.js ecosystem is large but not all that much geared towards low level automation
### Rust
* Good, because Rust has a good type system allowing to prevent many bugs and crashes using static analysis built
into the compiler.
* Good, because Rust code is very efficient.
* Good, because many people are very interested in learning Rust and are only lacking a reason to.
* Bad, because speed of development is slower than for most other options.
* Bad, because only one developer in the group has any experience with Rust.
* Bad, because available developer pool is small.
### Erlang
* Good, because specifically built for highly-available, crash-resistant software
* Good, because it offers an extremely fine-grained crashing system with encapsulated processes that can not take each
other down
* Good, because the ecosystem and libraries of Erlang are generally geared towards the problem domain of FabAccess
* Good, because Erlang allows for significant static analysis compared to TypeScript, Pyton and Ruby
* Bad, because Erlang has a worse type system than Rust and Haskell.
* Bad, because only one developer in the group has any experience with Erlang.
* Bad, because the developer pool is the second smallest.
### Haskell
* Good, because strongest type system allowing to prevent many bugs and crashes using static analysis built into the
compiler
* Bad, because only one developer has any experience with Haskell.
* Bad, because the developer pool is the smallest.
## Links <!-- optional -->
* Discussions documented in:
- [Protokoll Jit.si-Konferenz 09.12.19](https://pad.gwdg.de/obbgQwKiQNmRRwDt1yERew?view#Kuratiertes-Framework-vs-Erweiterbarer-Monolith)
- [Pad "Roseguarden"](https://pad.gwdg.de/v-xVnpWQREmBGuLG_hYTfQ#Projektinterne-Werkzeuge)
- [Pad BF²H "Grundlegendes"](https://pad.gwdg.de/XrReiGdCS-GfcWhEKgjTBA#)
- [JitSi Call 18.02](https://pad.gwdg.de/71fd449SRgGm_HhL4rgVqw#)

14
docs/decisions/index.md Normal file
View File

@ -0,0 +1,14 @@
# Architectural Decision Log
This log lists the architectural decisions for Diflouroborane (BFFH).
See also the ADR log of [FabAccess-API](https://gitlab.com/fabinfra/fabaccess/fabaccess-api/-/tree/main/docs/decisions).
<!-- adrlog -- Regenerate the content by using "adr-log -i". You can install it via "npm install -g adr-log" -->
* [ADR-0000](0000-Programming-language-and-framework-choice.md) - Choosing a programming Language and Framework for BFFH
<!-- adrlogstop -->
For new ADRs, please use [template.md](template.md) as basis.
More information on MADR is available at <https://adr.github.io/madr/>.
General information about architectural decision records is available at <https://adr.github.io/>.

View File

@ -0,0 +1,72 @@
# [short title of solved problem and solution]
* Status: [proposed | rejected | accepted | deprecated | … | superseded by [ADR-0005](0005-example.md)] <!-- optional -->
* Deciders: [list everyone involved in the decision] <!-- optional -->
* Date: [YYYY-MM-DD when the decision was last updated] <!-- optional -->
Technical Story: [description | ticket/issue URL] <!-- optional -->
## Context and Problem Statement
[Describe the context and problem statement, e.g., in free form using two to three sentences. You may want to articulate the problem in form of a question.]
## Decision Drivers <!-- optional -->
* [driver 1, e.g., a force, facing concern, …]
* [driver 2, e.g., a force, facing concern, …]
* … <!-- numbers of drivers can vary -->
## Considered Options
* [option 1]
* [option 2]
* [option 3]
* … <!-- numbers of options can vary -->
## Decision Outcome
Chosen option: "[option 1]", because [justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force force | … | comes out best (see below)].
### Positive Consequences <!-- optional -->
* [e.g., improvement of quality attribute satisfaction, follow-up decisions required, …]
* …
### Negative Consequences <!-- optional -->
* [e.g., compromising quality attribute, follow-up decisions required, …]
* …
## Pros and Cons of the Options <!-- optional -->
### [option 1]
[example | description | pointer to more information | …] <!-- optional -->
* Good, because [argument a]
* Good, because [argument b]
* Bad, because [argument c]
* … <!-- numbers of pros and cons can vary -->
### [option 2]
[example | description | pointer to more information | …] <!-- optional -->
* Good, because [argument a]
* Good, because [argument b]
* Bad, because [argument c]
* … <!-- numbers of pros and cons can vary -->
### [option 3]
[example | description | pointer to more information | …] <!-- optional -->
* Good, because [argument a]
* Good, because [argument b]
* Bad, because [argument c]
* … <!-- numbers of pros and cons can vary -->
## Links <!-- optional -->
* [Link type] [Link to ADR] <!-- example: Refined by [ADR-0005](0005-example.md) -->
* … <!-- numbers of links can vary -->