gatewayd: config, [approvals] users and socket
Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
@@ -17,6 +17,18 @@ pub struct Config {
|
||||
pub paths: Paths,
|
||||
#[serde(default)]
|
||||
pub limits: Limits,
|
||||
#[serde(default)]
|
||||
pub approvals: ApprovalsConfig,
|
||||
}
|
||||
|
||||
/// `[approvals]`: who may answer approvals, and where `brokerd` brings them.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields, default)]
|
||||
pub struct ApprovalsConfig {
|
||||
/// Mattermost user ids; empty means everyone in `[allow] users`.
|
||||
pub users: Vec<String>,
|
||||
/// Empty: `<home>/run/gateway/gateway.sock`.
|
||||
pub socket: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
@@ -184,6 +196,13 @@ impl Config {
|
||||
));
|
||||
}
|
||||
}
|
||||
for id in &self.approvals.users {
|
||||
if !self.allow.users.contains(id) {
|
||||
return Some(format!(
|
||||
"[approvals] {id:?} is not in [allow] users: an approver who is not heard could never answer"
|
||||
));
|
||||
}
|
||||
}
|
||||
if self.limits.queue == 0 {
|
||||
return Some("[limits] queue must be at least 1".to_string());
|
||||
}
|
||||
@@ -221,6 +240,27 @@ impl Config {
|
||||
self.loop_.socket.clone()
|
||||
}
|
||||
|
||||
/// Who may answer approvals: `[approvals] users`, or `[allow] users` when that is empty.
|
||||
pub fn approvers(&self) -> &[String] {
|
||||
if self.approvals.users.is_empty() {
|
||||
return &self.allow.users;
|
||||
}
|
||||
&self.approvals.users
|
||||
}
|
||||
|
||||
/// The first approver, or `None` when there are none.
|
||||
pub fn first_approver(&self) -> Option<&str> {
|
||||
self.approvers().first().map(String::as_str)
|
||||
}
|
||||
|
||||
/// `gateway.sock`: `[approvals] socket`, or `<home>/run/gateway/gateway.sock` when empty.
|
||||
pub fn gateway_socket(&self) -> PathBuf {
|
||||
if self.approvals.socket.as_os_str().is_empty() {
|
||||
return self.paths.home.join("run/gateway/gateway.sock");
|
||||
}
|
||||
self.approvals.socket.clone()
|
||||
}
|
||||
|
||||
pub fn state_path(&self) -> PathBuf {
|
||||
// <home>/gateway/state.json.
|
||||
self.paths.home.join("gateway/state.json")
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
//! `[approvals]` in `gatewayd.toml`: who may answer, the first approver, and `gateway.sock`
|
||||
//! (M4b spec, section 3). Do not edit.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use gatewayd::config::Config;
|
||||
|
||||
const KYLE: &str = "k0000000000000000000000000";
|
||||
const HERMES: &str = "h0000000000000000000000000";
|
||||
const OTHER: &str = "o0000000000000000000000000";
|
||||
|
||||
fn text(approvals: &str) -> String {
|
||||
format!(
|
||||
"[mattermost]\nurl = \"https://chat.example\"\n[secrets.mattermost_token]\nenv = \"T\"\n[allow]\nusers = [\"{KYLE}\", \"{HERMES}\"]\n[paths]\nhome = \"/srv/bx\"\n{approvals}"
|
||||
)
|
||||
}
|
||||
|
||||
fn config(approvals: &str) -> Config {
|
||||
let c = Config::parse(&text(approvals)).unwrap();
|
||||
assert_eq!(c.problem(), None);
|
||||
c
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_the_section_everyone_allowed_may_answer() {
|
||||
let c = config("");
|
||||
assert_eq!(c.approvers(), [KYLE, HERMES]);
|
||||
assert_eq!(c.first_approver(), Some(KYLE));
|
||||
assert_eq!(
|
||||
c.gateway_socket(),
|
||||
PathBuf::from("/srv/bx/run/gateway/gateway.sock")
|
||||
);
|
||||
assert_eq!(
|
||||
config("[approvals]\nusers = []\n").approvers(),
|
||||
[KYLE, HERMES]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_list_narrows_who_may_answer() {
|
||||
let c = config(&format!(
|
||||
"[approvals]\nusers = [\"{HERMES}\"]\nsocket = \"/run/gw.sock\"\n"
|
||||
));
|
||||
assert_eq!(c.approvers(), [HERMES]);
|
||||
assert_eq!(c.first_approver(), Some(HERMES));
|
||||
assert_eq!(c.gateway_socket(), PathBuf::from("/run/gw.sock"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_approver_must_be_heard() {
|
||||
let c = Config::parse(&text(&format!(
|
||||
"[approvals]\nusers = [\"{KYLE}\", \"{OTHER}\"]\n"
|
||||
)))
|
||||
.unwrap();
|
||||
let why = c.problem().expect("refused");
|
||||
assert_eq!(
|
||||
why,
|
||||
format!(
|
||||
"[approvals] \"{OTHER}\" is not in [allow] users: an approver who is not heard could never answer"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_keys_are_errors() {
|
||||
assert!(Config::parse(&text("[approvals]\napprovers = []\n")).is_err());
|
||||
assert!(Config::parse(&text("[approvals]\nusers = []\nttl = 5\n")).is_err());
|
||||
}
|
||||
Reference in New Issue
Block a user