Commit

Author:

Hash:

Timestamp:

+353 -215 +/-18 browse

Kevin Schoon [me@kevinschoon.com]

971a541e67075e5a25727aa39292836fdf1e4a10

Sat, 06 Jul 2024 10:25:08 +0000 (9 months ago)

fixup various scripts
1diff --git a/Cargo.lock b/Cargo.lock
2index 80e6320..eeeab47 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -502,7 +502,7 @@ dependencies = [
6 "ayllu_rpc",
7 "bytes",
8 "cc",
9- "clap 4.5.3",
10+ "clap 4.5.9",
11 "comrak",
12 "env_logger 0.10.2",
13 "file-mode",
14 @@ -555,7 +555,7 @@ dependencies = [
15 "ayllu_api",
16 "ayllu_config",
17 "ayllu_rpc",
18- "clap 4.5.3",
19+ "clap 4.5.9",
20 "clap_complete",
21 "futures",
22 "mailpot",
23 @@ -579,9 +579,13 @@ dependencies = [
24 name = "ayllu_config"
25 version = "0.2.1"
26 dependencies = [
27+ "clap 4.5.9",
28 "log",
29 "serde",
30+ "serde_json",
31+ "thiserror",
32 "toml 0.7.8",
33+ "toml_edit 0.22.14",
34 ]
35
36 [[package]]
37 @@ -878,9 +882,9 @@ dependencies = [
38
39 [[package]]
40 name = "clap"
41- version = "4.5.3"
42+ version = "4.5.9"
43 source = "registry+https://github.com/rust-lang/crates.io-index"
44- checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
45+ checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
46 dependencies = [
47 "clap_builder",
48 "clap_derive",
49 @@ -888,9 +892,9 @@ dependencies = [
50
51 [[package]]
52 name = "clap_builder"
53- version = "4.5.2"
54+ version = "4.5.9"
55 source = "registry+https://github.com/rust-lang/crates.io-index"
56- checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
57+ checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942"
58 dependencies = [
59 "anstream",
60 "anstyle",
61 @@ -905,14 +909,14 @@ version = "4.5.1"
62 source = "registry+https://github.com/rust-lang/crates.io-index"
63 checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c"
64 dependencies = [
65- "clap 4.5.3",
66+ "clap 4.5.9",
67 ]
68
69 [[package]]
70 name = "clap_derive"
71- version = "4.5.3"
72+ version = "4.5.8"
73 source = "registry+https://github.com/rust-lang/crates.io-index"
74- checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f"
75+ checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
76 dependencies = [
77 "heck 0.5.0",
78 "proc-macro2",
79 @@ -944,7 +948,7 @@ version = "0.18.0"
80 source = "registry+https://github.com/rust-lang/crates.io-index"
81 checksum = "482aa5695bca086022be453c700a40c02893f1ba7098a2c88351de55341ae894"
82 dependencies = [
83- "clap 4.5.3",
84+ "clap 4.5.9",
85 "entities",
86 "memchr",
87 "once_cell",
88 @@ -3902,7 +3906,7 @@ dependencies = [
89 "ayllu_config",
90 "ayllu_git",
91 "ayllu_rpc",
92- "clap 4.5.3",
93+ "clap 4.5.9",
94 "clap_complete",
95 "reqwest 0.12.3",
96 "serde",
97 @@ -4446,9 +4450,9 @@ dependencies = [
98
99 [[package]]
100 name = "serde_json"
101- version = "1.0.114"
102+ version = "1.0.120"
103 source = "registry+https://github.com/rust-lang/crates.io-index"
104- checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
105+ checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
106 dependencies = [
107 "itoa",
108 "ryu",
109 @@ -5120,18 +5124,18 @@ dependencies = [
110
111 [[package]]
112 name = "thiserror"
113- version = "1.0.58"
114+ version = "1.0.61"
115 source = "registry+https://github.com/rust-lang/crates.io-index"
116- checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
117+ checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
118 dependencies = [
119 "thiserror-impl",
120 ]
121
122 [[package]]
123 name = "thiserror-impl"
124- version = "1.0.58"
125+ version = "1.0.61"
126 source = "registry+https://github.com/rust-lang/crates.io-index"
127- checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
128+ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
129 dependencies = [
130 "proc-macro2",
131 "quote",
132 diff --git a/ayllu/src/main.rs b/ayllu/src/main.rs
133index 008fdae..d6c4717 100644
134--- a/ayllu/src/main.rs
135+++ b/ayllu/src/main.rs
136 @@ -46,21 +46,10 @@ struct JobArguments {
137 }
138
139 #[derive(Subcommand, Debug)]
140- enum ConfigCommand {
141- /// Display the current configuration.
142- Display {
143- #[clap(long, default_value = "false")]
144- as_json: bool,
145- },
146- /// Generate a sample configuration file.
147- Generate,
148- }
149-
150- #[derive(Subcommand, Debug)]
151 enum Command {
152 /// Configuration options.
153 #[clap(subcommand)]
154- Config(ConfigCommand),
155+ Config(ayllu_config::Command),
156 /// Perform database migration.
157 Migrate {
158 #[clap(short, long)]
159 @@ -124,25 +113,9 @@ fn do_migration(config: &config::Database, path: Option<String>) -> Result<(), D
160 fn main() -> Result<(), Box<dyn Error>> {
161 let args: Arguments = Arguments::parse();
162 match args.subcommand {
163- Command::Config(subcommand) => match subcommand {
164- ConfigCommand::Display { as_json: true } => {
165- println!(
166- "{}",
167- init_config(args.config.as_deref(), args.level.as_deref())?.to_json()
168- );
169- Ok(())
170- }
171- ConfigCommand::Display { as_json: false } => {
172- println!(
173- "{:#?}",
174- init_config(args.config.as_deref(), args.level.as_deref())?
175- );
176- Ok(())
177- }
178- ConfigCommand::Generate => {
179- println!("{}", config::EXAMPLE_CONFIG);
180- Ok(())
181- }
182+ Command::Config(subcommand) => {
183+ subcommand.execute::<config::Config>(config::EXAMPLE_CONFIG, args.config)?;
184+ Ok(())
185 },
186 Command::Migrate { migrations } => {
187 let cfg = init_config(args.config.as_deref(), args.level.as_deref())?;
188 diff --git a/containers/ayllu/Containerfile b/containers/ayllu/Containerfile
189index 84d9388..a7a3af0 100644
190--- a/containers/ayllu/Containerfile
191+++ b/containers/ayllu/Containerfile
192 @@ -1,5 +1,5 @@
193- ARG BASE_BUILD_IMAGE=registry.ayllu-forge.org/ayllu/base-build
194- FROM $BASE_BUILD_IMAGE AS build
195+ ARG BUILD_IMAGE=registry.ayllu-forge.org/ayllu/ayllu:base-build
196+ FROM $BUILD_IMAGE AS build
197
198 ARG TREE_SITTER_DIFF_UPSTREAM_URL="https://github.com/the-mikedavis/tree-sitter-diff/archive"
199 ARG TREE_SITTER_DIFF_COMMIT_ID="629676fc3919606964231b2c7b9677d6998a2cb4"
200 diff --git a/containers/multiuser-mail/Containerfile b/containers/multiuser-mail/Containerfile
201index 90cd251..d6a8692 100644
202--- a/containers/multiuser-mail/Containerfile
203+++ b/containers/multiuser-mail/Containerfile
204 @@ -1,6 +1,5 @@
205- # milter and other mail security utilities from
206- ARG BASE_BUILD_IMAGE
207- FROM $BASE_BUILD_IMAGE AS build
208+ ARG BUILD_IMAGE
209+ FROM $BUILD_IMAGE AS build
210
211 ARG DKIMDO_VERSION="0.1.1"
212 ARG DKIM_MILTER="0.1.0"
213 diff --git a/containers/multiuser/init/config.sh b/containers/multiuser/init/config.sh
214index 31b993d..57f51e5 100755
215--- a/containers/multiuser/init/config.sh
216+++ b/containers/multiuser/init/config.sh
217 @@ -4,4 +4,4 @@
218 # at runtime e.g. ayllu config set http.address = ....
219 [ -n "${AYLLU_LISTEN_ADDRESS}" ] && {
220 sed -i "s/127.0.0.1:8080/$AYLLU_LISTEN_ADDRESS/" /etc/ayllu/config.toml
221- } || true
222+ }
223 diff --git a/containers/multiuser/init/users.sh b/containers/multiuser/init/users.sh
224index 8d1a791..ad75133 100755
225--- a/containers/multiuser/init/users.sh
226+++ b/containers/multiuser/init/users.sh
227 @@ -4,7 +4,6 @@ set -e
228
229 # first setup the ayllu user
230 AYLLU_HOME="/home/ayllu"
231- AYLLU_CONFIG="/etc/ayllu/config.toml"
232 AYLLU_SSH_AUTHORIZED_KEYS_FILE="$AYLLU_HOME/.ssh/authorized_keys"
233
234 mkdir -p /var/lib/ayllu
235 @@ -33,7 +32,7 @@ do
236 adduser -h "/home/$username" -D -g "Ayllu Managed User" "$username"
237 # create a directory called "repos" which we give full access to both the
238 # user and Ayllu.
239- mkdir "/home/$username/repos"
240+ mkdir -p "/home/$username/repos"
241 chmod g+s "/home/$username/repos"
242 setfacl -d -m g::rwx "/home/$username/repos"
243
244 diff --git a/containers/multiuser/service/dropbear/run b/containers/multiuser/service/dropbear/run
245index 3a10acd..1d82859 100755
246--- a/containers/multiuser/service/dropbear/run
247+++ b/containers/multiuser/service/dropbear/run
248 @@ -45,4 +45,4 @@ if [ -n "$DROPBEAR_SSH_PORT" ] ; then
249 DROPBEAR_FLAGS="$DROPBEAR_FLAGS -p $DROPBEAR_SSH_PORT"
250 fi
251
252- exec dropbear $DROPBEAR_FLAGS
253+ exec dropbear "$DROPBEAR_FLAGS"
254 diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml
255index ab1f5af..c042d5b 100644
256--- a/crates/config/Cargo.toml
257+++ b/crates/config/Cargo.toml
258 @@ -9,3 +9,7 @@ edition = "2021"
259 toml = "0.7.4"
260 serde = { version = "1.0", features = ["derive"] }
261 log = "0.4.19"
262+ toml_edit = { version = "0.22.14", features = ["serde"] }
263+ thiserror = "1.0.61"
264+ clap = "4.5.9"
265+ serde_json = "1.0.120"
266 diff --git a/crates/config/src/edit.rs b/crates/config/src/edit.rs
267new file mode 100644
268index 0000000..4a104df
269--- /dev/null
270+++ b/crates/config/src/edit.rs
271 @@ -0,0 +1,123 @@
272+ use std::path::PathBuf;
273+
274+ use toml_edit::visit_mut::VisitMut;
275+ use toml_edit::{DocumentMut, Formatted, Item, Value};
276+
277+ use crate::error::Error;
278+ use crate::reader::try_read;
279+
280+ /// convert parsed serde_toml values into toml_edit "annotated" values
281+ pub fn v2v(value: toml::Value) -> Value {
282+ match value {
283+ toml::Value::String(v) => Value::String(Formatted::new(v.to_string())),
284+ toml::Value::Integer(v) => Value::Integer(Formatted::new(v)),
285+ toml::Value::Float(v) => Value::Float(Formatted::new(v)),
286+ toml::Value::Boolean(v) => Value::Boolean(Formatted::new(v)),
287+ toml::Value::Datetime(v) => Value::Datetime(Formatted::new(v)),
288+ toml::Value::Array(v) => v.into_iter().map(v2v).collect(),
289+ toml::Value::Table(v) => v.into_iter().map(|(k, v)| (k, v2v(v))).collect(),
290+ }
291+ }
292+
293+ #[derive(Default)]
294+ struct Visitor {
295+ pub target: String,
296+ pub state: Vec<String>,
297+ pub replace: Option<toml::Value>,
298+ pub found: Option<Value>,
299+ }
300+
301+ impl VisitMut for Visitor {
302+ fn visit_table_like_kv_mut(&mut self, key: toml_edit::KeyMut<'_>, node: &mut Item) {
303+ if self.found.is_some() {
304+ return;
305+ }
306+ match node {
307+ Item::None => {}
308+ Item::Value(value) => {
309+ let mut copy = self.state.clone();
310+ copy.append(&mut vec![key.to_string()]);
311+ if copy.join(".") == self.target {
312+ if let Some(new_value) = &self.replace {
313+ *value = v2v(new_value.clone());
314+ }
315+ self.found = Some(value.clone());
316+ }
317+ self.state.clear();
318+ }
319+ Item::Table(_) => {
320+ self.state.push(key.to_string());
321+ toml_edit::visit_mut::visit_table_like_kv_mut(self, key, node);
322+ }
323+ Item::ArrayOfTables(_) => {
324+ // arrays are not supported
325+ }
326+ }
327+ }
328+ }
329+
330+ /// Editor is can perform limited modification Ayllu configuration files.
331+ /// It only supports modifying simple key values with a restricted set of
332+ /// value types: bool, string, and numbers for the moment.
333+ /// TODO: consider expanding this to support more configuration
334+ pub struct Editor(pub Option<PathBuf>);
335+
336+ impl Editor {
337+ fn get_cfg(&self) -> Result<(PathBuf, String), Error> {
338+ if let Some(path) = &self.0 {
339+ Ok((path.clone(), std::fs::read_to_string(path)?))
340+ } else if let Some((path, str_cfg)) = try_read() {
341+ Ok((path, str_cfg))
342+ } else {
343+ Err(Error::ConfigNotFound)
344+ }
345+ }
346+
347+ /// Return the configuration value, if no key is specified then return the
348+ /// entire configuration. If as_json is true then return a JSON
349+ /// representation rather than TOML.
350+ pub fn get(&self, key: &str) -> Result<String, Error> {
351+ let (_, cfg_str) = self.get_cfg()?;
352+ let mut doc = cfg_str.parse::<DocumentMut>()?;
353+ let mut writer = Visitor {
354+ target: key.to_string(),
355+ replace: None,
356+ state: vec![],
357+ found: None,
358+ };
359+ writer.visit_document_mut(&mut doc);
360+ if let Some(value) = writer.found {
361+ Ok(value.to_string())
362+ } else {
363+ Err(Error::KeyNotFound(key.to_string()))
364+ }
365+ }
366+
367+ /// Set a configuration option at the given path. For example to set a
368+ /// value such as:
369+ /// [builder]
370+ /// address = "/tmp/fuu.socket"
371+ /// [builder.database]
372+ /// migrate = true
373+ ///
374+ /// You can use the key_path builder.database
375+ /// NOTE: That modifying arrays is unsupported and they may only be
376+ /// clobbered.
377+ /// NOTE: String values must be escaped such as \"fuu\"
378+ pub fn set(&self, key_path: &str, value_str: &str) -> Result<(), Error> {
379+ // BUG: this is a hack, I can't figure out how to do this properly via
380+ // serde / serde_toml, maybe it's not possible, I don't know.
381+ let value: toml::value::Value = toml::from_str(&format!("x = {}", value_str))?;
382+ let value = value.get("x").unwrap();
383+ let (cfg_path, cfg_str) = self.get_cfg()?;
384+ let mut doc = cfg_str.parse::<DocumentMut>()?;
385+ let mut writer = Visitor {
386+ target: key_path.to_string(),
387+ replace: Some(value.clone()),
388+ ..Default::default()
389+ };
390+ writer.visit_document_mut(&mut doc);
391+ std::fs::write(cfg_path, doc.to_string().as_bytes())?;
392+ Ok(())
393+ }
394+ }
395 diff --git a/crates/config/src/error.rs b/crates/config/src/error.rs
396index 980b2fa..968a74d 100644
397--- a/crates/config/src/error.rs
398+++ b/crates/config/src/error.rs
399 @@ -1,70 +1,28 @@
400- use std::error::Error as StdError;
401- use std::fmt::Display;
402 use std::io::Error as IoError;
403
404- use toml::de::Error as TomlError;
405-
406- #[derive(Debug)]
407- pub enum ErrorKind {
408- Io(IoError),
409- Serialization(TomlError),
410+ use serde_json::error::Error as JsonError;
411+ use toml::{de::Error as TomlDeserializeError, ser::Error as TomlSerializeError};
412+ use toml_edit::TomlError as TomlEditError;
413+
414+ /// Configuration error
415+ #[derive(thiserror::Error, Debug)]
416+ pub enum Error {
417+ #[error("Io error: {0}")]
418+ Io(#[from] IoError),
419+ #[error("De-Serialization (toml) error: {0}")]
420+ TomlDeSerialization(#[from] TomlDeserializeError),
421+ #[error("Serialization (toml) error: {0}")]
422+ TomlSerialization(#[from] TomlSerializeError),
423+ #[error("Serialization (json) error: {0}")]
424+ JsonSerialization(#[from] JsonError),
425+ #[error("Configuration edit failure: {0}")]
426+ EditFailed(#[from] TomlEditError),
427+ #[error("Configuration file could not be found")]
428 ConfigNotFound,
429- Other(String),
430- }
431-
432- #[derive(Debug)]
433- pub struct Error {
434- kind: ErrorKind,
435- }
436-
437- impl Error {
438- pub fn kind(kind: ErrorKind) -> Self {
439- Error { kind }
440- }
441- }
442-
443- impl From<IoError> for Error {
444- fn from(value: IoError) -> Self {
445- Error {
446- kind: ErrorKind::Io(value),
447- }
448- }
449- }
450-
451- impl From<TomlError> for Error {
452- fn from(value: TomlError) -> Self {
453- Error {
454- kind: ErrorKind::Serialization(value),
455- }
456- }
457- }
458-
459- impl From<Box<dyn StdError>> for Error {
460- fn from(value: Box<dyn StdError>) -> Self {
461- Error {
462- kind: ErrorKind::Other(value.to_string()),
463- }
464- }
465- }
466-
467- impl Display for Error {
468- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
469- match self.kind {
470- ErrorKind::Io(ref err) => err.fmt(f),
471- ErrorKind::Serialization(ref err) => err.fmt(f),
472- ErrorKind::Other(ref msg) => write!(f, "validation: {}", msg),
473- ErrorKind::ConfigNotFound => write!(f, "failed to detect a config file"),
474- }
475- }
476- }
477-
478- impl StdError for Error {
479- fn source(&self) -> Option<&(dyn StdError + 'static)> {
480- match self.kind {
481- ErrorKind::Io(ref err) => Some(err),
482- ErrorKind::Serialization(ref err) => Some(err),
483- ErrorKind::Other(_) => None,
484- ErrorKind::ConfigNotFound => None,
485- }
486- }
487+ #[error("Configuration key could not be found: {0}")]
488+ KeyNotFound(String),
489+ #[error("Error initializing configuration: {0}")]
490+ Initialization(String),
491+ #[error("Error validating configuration: {0}")]
492+ Validation(String),
493 }
494 diff --git a/crates/config/src/flags.rs b/crates/config/src/flags.rs
495new file mode 100644
496index 0000000..8d74d74
497--- /dev/null
498+++ b/crates/config/src/flags.rs
499 @@ -0,0 +1,68 @@
500+ use std::path::PathBuf;
501+
502+ use clap::Subcommand;
503+ use serde::{de::DeserializeOwned, ser::Serialize};
504+ use serde_json::to_string as to_json_str;
505+ use toml::ser::to_string as to_toml_string;
506+
507+ use crate::edit::Editor;
508+ use crate::error::Error;
509+ use crate::reader::{Configurable, Reader};
510+
511+ /// Subcommand for managing configuration across different ayllu binaries.
512+ #[derive(Subcommand, Debug)]
513+ pub enum Command {
514+ /// Retrieve a value from the configuration
515+ Get {
516+ /// dot separated key used to select a value within the config file
517+ key: String,
518+ },
519+ /// Set a value in the configuration
520+ Set {
521+ /// dot separated key used to select a value within the config file
522+ key: String,
523+ /// string representation of a TOML value to be placed in the config
524+ value: String,
525+ },
526+ /// Display the current configuration.
527+ Display {
528+ #[clap(long, default_value = "false")]
529+ as_json: bool,
530+ },
531+ /// Generate a sample configuration file.
532+ Generate,
533+ }
534+
535+ impl Command {
536+ pub fn execute<T>(self, example_cfg: &str, path: Option<PathBuf>) -> Result<(), Error>
537+ where
538+ T: DeserializeOwned + Configurable + Serialize,
539+ {
540+ match self {
541+ Command::Get { key } => {
542+ let value = Editor(path).get(&key)?;
543+ println!("{}", value);
544+ Ok(())
545+ }
546+ Command::Set { key, value } => Editor(path).set(&key, &value),
547+ Command::Display { as_json } => {
548+ let mut cfg: T = Reader::load(path.as_deref())?;
549+ if as_json {
550+ cfg.validate()
551+ .map_err(|e| Error::Validation(e.to_string()))?;
552+ println!("{}", to_json_str(&cfg)?);
553+ Ok(())
554+ } else {
555+ cfg.validate()
556+ .map_err(|e| Error::Validation(e.to_string()))?;
557+ println!("{}", to_toml_string(&cfg)?);
558+ Ok(())
559+ }
560+ }
561+ Command::Generate => {
562+ print!("{}", example_cfg);
563+ Ok(())
564+ }
565+ }
566+ }
567+ }
568 diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs
569index 04fb65e..03d898a 100644
570--- a/crates/config/src/lib.rs
571+++ b/crates/config/src/lib.rs
572 @@ -1,5 +1,9 @@
573+ pub use edit::Editor;
574 pub use error::Error;
575+ pub use flags::Command;
576 pub use reader::{data_dir, runtime_dir, Configurable, Reader};
577
578+ mod edit;
579 mod error;
580+ mod flags;
581 mod reader;
582 diff --git a/crates/config/src/reader.rs b/crates/config/src/reader.rs
583index abb4889..d8af253 100644
584--- a/crates/config/src/reader.rs
585+++ b/crates/config/src/reader.rs
586 @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
587 use serde::de::DeserializeOwned;
588 use toml::from_str;
589
590- use crate::error::{Error, ErrorKind};
591+ use crate::error::Error;
592
593 pub trait Configurable {
594 /// initialize the configuration (typically set defaults)
595 @@ -19,6 +19,32 @@ pub trait Configurable {
596 }
597 }
598
599+ /// attempt to read the configuration from the host
600+ pub fn try_read() -> Option<(PathBuf, String)> {
601+ let home_config = match env::var("XDG_CONFIG_HOME") {
602+ Ok(path) => PathBuf::from_iter([path.as_str(), "ayllu", "config.toml"]),
603+ Err(_) => PathBuf::from_iter([
604+ env::var("HOME").unwrap_or(String::from("/")).as_str(),
605+ ".config",
606+ "ayllu",
607+ "config.toml",
608+ ]),
609+ };
610+ for path in [
611+ &home_config,
612+ Path::new("./config.toml"),
613+ Path::new("/etc/ayllu/config.toml"),
614+ ] {
615+ log::info!("reading configuration from {}", path.display());
616+ let ret = std::fs::read_to_string(path);
617+ match ret {
618+ Ok(r) => return Some((path.to_path_buf(), r)),
619+ Err(_) => continue,
620+ };
621+ }
622+ None
623+ }
624+
625 /// Reader the global configuration file
626 pub struct Reader<T>(T);
627
628 @@ -26,52 +52,35 @@ impl<T> Reader<T>
629 where
630 T: DeserializeOwned + Configurable,
631 {
632- fn _try_read() -> Option<String> {
633- let home_config = match env::var("XDG_CONFIG_HOME") {
634- Ok(path) => PathBuf::from_iter([path.as_str(), "ayllu", "config.toml"]),
635- Err(_) => PathBuf::from_iter([
636- env::var("HOME").unwrap_or(String::from("/")).as_str(),
637- ".config",
638- "ayllu",
639- "config.toml",
640- ]),
641- };
642- for path in [
643- &home_config,
644- Path::new("./config.toml"),
645- Path::new("/etc/ayllu/config.toml"),
646- ] {
647- log::info!("reading configuration from {}", path.display());
648- let ret = std::fs::read_to_string(path);
649- match ret {
650- Ok(r) => return Some(r),
651- Err(_) => continue,
652- };
653- }
654- None
655- }
656-
657- /// load a configuration from a given path, if the path is not specified
658+ /// load a configuration from a given path, if the path is not specified
659 /// then attempt to load the config from common defaults.
660 pub fn load(path: Option<&Path>) -> Result<T, Error> {
661 let config_str = match path {
662 Some(path) => std::fs::read_to_string(path)?,
663- None => match Reader::<T>::_try_read() {
664- Some(config_str) => config_str,
665- None => return Err(Error::kind(ErrorKind::ConfigNotFound)),
666+ None => match try_read() {
667+ Some((_, config_str)) => config_str,
668+ None => return Err(Error::ConfigNotFound),
669 },
670 };
671 let mut config = from_str::<T>(&config_str)?;
672- config.initialize()?;
673- config.validate()?;
674+ config
675+ .initialize()
676+ .map_err(|e| Error::Initialization(e.to_string()))?;
677+ config
678+ .validate()
679+ .map_err(|e| Error::Validation(e.to_string()))?;
680 Ok(config)
681 }
682
683 /// load the config from a string
684 pub fn read(config_str: &str) -> Result<T, Error> {
685 let mut config = from_str::<T>(config_str)?;
686- config.initialize()?;
687- config.validate()?;
688+ config
689+ .initialize()
690+ .map_err(|e| Error::Initialization(e.to_string()))?;
691+ config
692+ .validate()
693+ .map_err(|e| Error::Validation(e.to_string()))?;
694 Ok(config)
695 }
696 }
697 diff --git a/scripts/build_and_deploy.sh b/scripts/build_and_deploy.sh
698index 51b33ee..0e00c94 100755
699--- a/scripts/build_and_deploy.sh
700+++ b/scripts/build_and_deploy.sh
701 @@ -2,6 +2,7 @@
702 # Internal helper script to build and deploy Ayllu in one command
703
704 scripts/build_all_containers.sh
705+ scripts/push_all_containers.sh
706
707 /usr/lib/podman/quadlet --user "$HOME/.config/systemd/user"
708 systemctl --user daemon-reload
709 diff --git a/scripts/build_container.sh b/scripts/build_container.sh
710index 0a23bf1..e0664c6 100755
711--- a/scripts/build_container.sh
712+++ b/scripts/build_container.sh
713 @@ -1,11 +1,13 @@
714 #!/bin/sh
715- set -ex
716+ # Build a container image and tag it appropriately. For the base ayllu
717+ # container it's tag will look like registry.ayllu-forge.org/ayllu/ayllu:$BRANCH
718+ # Other containers will have ayllu:$name-$BRANCH
719+ set -e
720
721 REGISTRY="registry.ayllu-forge.org"
722+ IMAGE_PATH="ayllu/ayllu"
723 COMMIT_ID="$(git rev-parse HEAD)"
724 BRANCH_NAME="$(git branch --show-current)"
725- BASE_BUILD_IMAGE="$REGISTRY/ayllu/base-build:$COMMIT_ID"
726- EXTRA_ARGS="--build-arg=BASE_BUILD_IMAGE=$BASE_BUILD_IMAGE"
727
728 usage() {
729 printf "USAGE: build_container.sh PATH\n"
730 @@ -13,45 +15,28 @@ usage() {
731 }
732
733 TARGET_DIR="$1"
734-
735 [ -z "$TARGET_DIR" ] && usage
736
737- FLAVOR="$(basename "$TARGET_DIR")"
738- case "$FLAVOR" in
739- "base-build")
740- IMAGE_NAME="ayllu/base-build"
741- DETAILED_TAG="$COMMIT_ID"
742- FRIENDLY_TAG="$BRANCH_NAME"
743- ;;
744- "ayllu")
745- IMAGE_NAME="ayllu/ayllu"
746- DETAILED_TAG="$COMMIT_ID"
747- FRIENDLY_TAG="$BRANCH_NAME"
748- ;;
749- "multiuser")
750- IMAGE_NAME="ayllu/ayllu"
751- DETAILED_TAG="$FLAVOR-$COMMIT_ID"
752- FRIENDLY_TAG="$FLAVOR-$BRANCH_NAME"
753- EXTRA_ARGS="${EXTRA_ARGS} --build-arg=BASE_IMAGE=$REGISTRY/$IMAGE_NAME:$COMMIT_ID"
754- ;;
755- "multiuser-mail")
756- IMAGE_NAME="ayllu/ayllu"
757- DETAILED_TAG="$FLAVOR-$COMMIT_ID"
758- FRIENDLY_TAG="$FLAVOR-$BRANCH_NAME"
759- EXTRA_ARGS="${EXTRA_ARGS} --build-arg=BASE_IMAGE=$REGISTRY/$IMAGE_NAME:multiuser-$COMMIT_ID"
760- ;;
761- *)
762- echo "bad container name $FLAVOR"
763- exit 1
764- ;;
765- esac
766-
767- echo "building container $REGISTRY/$IMAGE_NAME:$DETAILED_TAG"
768+ NAME="$(basename "$TARGET_DIR")"
769+ TAG_NAME="$NAME-$COMMIT_ID"
770+ if [ "$NAME" = "ayllu" ] ; then
771+ TAG_NAME="$COMMIT_ID"
772+ fi
773
774 podman \
775 build --network=host \
776- -t "$REGISTRY/$IMAGE_NAME:$DETAILED_TAG" \
777- -f "$TARGET_DIR/Containerfile" ${EXTRA_ARGS} .
778+ -t "$REGISTRY/$IMAGE_PATH:$TAG_NAME" \
779+ -f "$TARGET_DIR/Containerfile" \
780+ --build-arg=BUILD_IMAGE="$REGISTRY/$IMAGE_PATH:base-build-$COMMIT_ID" \
781+ --build-arg=BASE_IMAGE="$REGISTRY/$IMAGE_PATH:$COMMIT_ID" \
782+ --build-arg=MULTIUSER_IMAGE="$REGISTRY/$IMAGE_PATH:multiuser-$COMMIT_ID" \
783+ .
784+
785+ if [ "$NAME" = "ayllu" ] ; then
786+ TARGET_TAG="$REGISTRY/$IMAGE_PATH:$BRANCH_NAME"
787+ else
788+ TARGET_TAG="$REGISTRY/$IMAGE_PATH:$NAME-$BRANCH_NAME"
789+ fi
790
791 podman tag \
792- "$REGISTRY/$IMAGE_NAME:$DETAILED_TAG" "$REGISTRY/$IMAGE_NAME:$FRIENDLY_TAG"
793+ "$REGISTRY/$IMAGE_PATH:$TAG_NAME" "$TARGET_TAG"
794 diff --git a/scripts/ensure_database.sh b/scripts/ensure_database.sh
795index 2f15919..4fae5b8 100755
796--- a/scripts/ensure_database.sh
797+++ b/scripts/ensure_database.sh
798 @@ -5,7 +5,7 @@ set -ex
799
800 COMPONENT="$1"
801
802- if [ ! -n "$COMPONENT" ] ; then
803+ if [ -z "$COMPONENT" ] ; then
804 COMPONENT="ayllu"
805 fi
806
807 @@ -14,7 +14,9 @@ DB_URL="sqlite://${DB_PATH}"
808
809 mkdir -p "$(dirname "${DB_PATH}")"
810
811- if [ ! -f ${DB_PATH} ]; then
812+ echo "$DB_PATH"
813+
814+ if [ ! -e "${DB_PATH}" ]; then
815 cargo sqlx database create --database-url "${DB_URL}"
816 fi
817
818 diff --git a/scripts/push_all_containers.sh b/scripts/push_all_containers.sh
819new file mode 100755
820index 0000000..728706d
821--- /dev/null
822+++ b/scripts/push_all_containers.sh
823 @@ -0,0 +1,7 @@
824+ #!/bin/sh
825+ set -e
826+
827+ scripts/push_container.sh containers/base-build
828+ scripts/push_container.sh containers/ayllu
829+ scripts/push_container.sh containers/multiuser
830+ scripts/push_container.sh containers/multiuser-mail
831 diff --git a/scripts/push_container.sh b/scripts/push_container.sh
832index c8be56d..7cbe68d 100755
833--- a/scripts/push_container.sh
834+++ b/scripts/push_container.sh
835 @@ -1,9 +1,16 @@
836 #!/bin/sh
837+ # Push a container image to the authenticated Ayllu registry endpoint. Note
838+ # that registry.ayllu-forge.org is for enduser consumption without
839+ # authentication while registry-auth.ayllu-forge.org is used to securely push
840+ # images into the registry and requries credentials.
841+ #
842+ # Assumes that the image you are pushing has already been built via the
843+ # build_container.sh script.
844 set -e
845
846 REGISTRY="registry.ayllu-forge.org"
847 REGISTRY_AUTH="registry-auth.ayllu-forge.org"
848- IMAGE_NAME="ayllu/ayllu"
849+ IMAGE_PATH="ayllu/ayllu"
850 COMMIT_ID="$(git rev-parse HEAD)"
851 BRANCH_NAME="$(git branch --show-current)"
852
853 @@ -13,24 +20,19 @@ usage() {
854 }
855
856 TARGET_DIR="$1"
857-
858 [ -z "$TARGET_DIR" ] && usage
859
860- FLAVOR="$(basename "$TARGET_DIR")"
861- if [ "$FLAVOR" = "base" ]; then
862- DETAILED_TAG="$COMMIT_ID"
863- FRIENDLY_TAG="$BRANCH_NAME"
864- else
865- DETAILED_TAG="$FLAVOR-$COMMIT_ID"
866- FRIENDLY_TAG="$FLAVOR-$BRANCH_NAME"
867- fi
868-
869+ NAME="$(basename "$TARGET_DIR")"
870 podman login "$REGISTRY_AUTH"
871
872- podman tag \
873- "$REGISTRY/$IMAGE_NAME:$DETAILED_TAG" "$REGISTRY_AUTH/$IMAGE_NAME:$DETAILED_TAG"
874- podman tag \
875- "$REGISTRY/$IMAGE_NAME:$FRIENDLY_TAG" "$REGISTRY_AUTH/$IMAGE_NAME:$FRIENDLY_TAG"
876-
877- podman push "$REGISTRY_AUTH/$IMAGE_NAME:$DETAILED_TAG"
878- podman push "$REGISTRY_AUTH/$IMAGE_NAME:$FRIENDLY_TAG"
879+ if [ "$NAME" = "ayllu" ] ; then
880+ podman tag "$REGISTRY/$IMAGE_PATH:$COMMIT_ID" "$REGISTRY_AUTH/$IMAGE_PATH:$COMMIT_ID"
881+ podman push "$REGISTRY_AUTH/$IMAGE_PATH:$COMMIT_ID"
882+ podman tag "$REGISTRY/$IMAGE_PATH:$BRANCH_NAME" "$REGISTRY_AUTH/$IMAGE_PATH:$BRANCH_NAME"
883+ podman push "$REGISTRY_AUTH/$IMAGE_PATH:$BRANCH_NAME"
884+ else
885+ podman tag "$REGISTRY/$IMAGE_PATH:$COMMIT_ID" "$REGISTRY_AUTH/$IMAGE_PATH:$NAME-$COMMIT_ID"
886+ podman push "$REGISTRY_AUTH/$IMAGE_PATH:$NAME-$COMMIT_ID"
887+ podman tag "$REGISTRY/$IMAGE_PATH:$BRANCH_NAME" "$REGISTRY_AUTH/$IMAGE_PATH:$NAME-$BRANCH_NAME"
888+ podman push "$REGISTRY_AUTH/$IMAGE_PATH:$NAME-$BRANCH_NAME"
889+ fi