Skip to content

Commit 09352a1

Browse files
authored
feat: Add user config support (#422)
* feat: Add basic user config reading mechanism * refactor: Simplify config reading * feat: Use user config value to disable version check
1 parent f3bc3f7 commit 09352a1

File tree

7 files changed

+226
-22
lines changed

7 files changed

+226
-22
lines changed

Cargo.lock

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.nix

Lines changed: 102 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git",
5454
tera = "1.20"
5555
termion = "4.0"
5656
tokio = { version = "1.38", features = ["rt-multi-thread", "macros", "fs", "process", "io-std"] }
57+
toml = { version = "0.9.8", features = ["serde"] }
5758
tower-http = { version = "0.5", features = ["validate-request"] }
5859
tracing = "0.1"
5960
tracing-indicatif = "0.3.9"

rust/stackablectl/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ serde.workspace = true
3030
snafu.workspace = true
3131
tera.workspace = true
3232
tokio.workspace = true
33+
toml.workspace = true
3334
tracing-subscriber.workspace = true
3435
tracing.workspace = true
3536
tracing-indicatif.workspace = true

rust/stackablectl/src/cli/mod.rs

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use std::{env, sync::Arc};
1+
use std::{env, path::Path, sync::Arc};
22

33
use clap::{Parser, Subcommand, ValueEnum};
44
use directories::ProjectDirs;
5-
use snafu::{ResultExt, Snafu};
5+
use snafu::{OptionExt, ResultExt, Snafu};
66
use stackable_cockpit::{
77
constants::{HELM_REPO_NAME_DEV, HELM_REPO_NAME_STABLE, HELM_REPO_NAME_TEST},
88
helm,
@@ -20,6 +20,7 @@ use tracing_indicatif::indicatif_eprintln;
2020
use crate::{
2121
args::{CommonFileArgs, CommonOperatorConfigsArgs, CommonRepoArgs},
2222
cmds::{cache, completions, debug, demo, operator, release, stack, stacklet, version},
23+
config::UserConfig,
2324
constants::{
2425
DEMOS_REPOSITORY_DEMOS_SUBPATH, DEMOS_REPOSITORY_STACKS_SUBPATH, DEMOS_REPOSITORY_URL_BASE,
2526
ENV_KEY_DEMO_FILES, ENV_KEY_RELEASE_FILES, ENV_KEY_STACK_FILES, REMOTE_RELEASE_FILE,
@@ -69,6 +70,9 @@ pub enum Error {
6970

7071
#[snafu(display("failed to initialize transfer client"))]
7172
InitializeTransferClient { source: xfer::Error },
73+
74+
#[snafu(display("failed to retrieve XDG directories"))]
75+
RetrieveXdgDirectories,
7276
}
7377

7478
#[derive(Debug, Snafu)]
@@ -169,27 +173,29 @@ impl Cli {
169173
Ok(())
170174
}
171175

172-
pub fn cache_settings(&self) -> Result<Settings, CacheSettingsError> {
176+
fn cache_settings(&self, cache_directory: &Path) -> Result<Settings, CacheSettingsError> {
173177
if self.no_cache {
174178
tracing::debug!("Cache disabled");
175179
Ok(Settings::disabled())
176180
} else {
177-
let project_dir = ProjectDirs::from(
178-
USER_DIR_QUALIFIER,
179-
USER_DIR_ORGANIZATION_NAME,
180-
USER_DIR_APPLICATION_NAME,
181-
)
182-
.ok_or(CacheSettingsError::UserDir)?;
183-
184-
let cache_dir = project_dir.cache_dir();
185181
tracing::debug!(
186-
cache_dir = %cache_dir.to_string_lossy(),
182+
cache_directory = %cache_directory.to_string_lossy(),
187183
"Setting cache directory"
188184
);
189-
Ok(Settings::disk(cache_dir))
185+
Ok(Settings::disk(cache_directory))
190186
}
191187
}
192188

189+
#[allow(clippy::result_large_err)]
190+
fn xdg_directories() -> Result<ProjectDirs, Error> {
191+
ProjectDirs::from(
192+
USER_DIR_QUALIFIER,
193+
USER_DIR_ORGANIZATION_NAME,
194+
USER_DIR_APPLICATION_NAME,
195+
)
196+
.context(RetrieveXdgDirectoriesSnafu)
197+
}
198+
193199
#[instrument(skip_all)]
194200
pub async fn run(self) -> Result<String, Error> {
195201
// FIXME (Techassi): There might be a better way to handle this with
@@ -202,7 +208,15 @@ impl Cli {
202208
_ => self.add_helm_repos().context(AddHelmReposSnafu)?,
203209
}
204210

205-
let cache_settings = self.cache_settings().context(RetrieveCacheSettingsSnafu)?;
211+
let xdg_directories = Cli::xdg_directories()?;
212+
// TODO (@Techassi): Move this file name to a constant
213+
let user_config_path = xdg_directories.config_dir().join("config.toml");
214+
215+
let user_config = UserConfig::from_file_or_default(user_config_path).unwrap();
216+
217+
let cache_settings = self
218+
.cache_settings(xdg_directories.cache_dir())
219+
.context(RetrieveCacheSettingsSnafu)?;
206220
let transfer_client = xfer::Client::new(cache_settings)
207221
.await
208222
.context(InitializeTransferClientSnafu)?;
@@ -225,13 +239,12 @@ impl Cli {
225239
.await;
226240

227241
// Only run the version check in the background if the user runs ANY other command than
228-
// the version command. Also only report if the current version is outdated.
229-
let check_version_in_background = !matches!(self.subcommand, Command::Version(_));
230-
let release_check_future = release_check::version_notice_output(
231-
transfer_client.clone(),
232-
check_version_in_background,
233-
true,
234-
);
242+
// the version command and the check isn't disabled via the user config. Also only report
243+
// if the current version is outdated.
244+
let check_version =
245+
!matches!(self.subcommand, Command::Version(_)) && user_config.version.check_enabled;
246+
let release_check_future =
247+
release_check::version_notice_output(transfer_client.clone(), check_version, true);
235248
let release_check_future =
236249
tokio::time::timeout(std::time::Duration::from_secs(2), release_check_future);
237250

rust/stackablectl/src/config.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use std::path::{Path, PathBuf};
2+
3+
use serde::Deserialize;
4+
use snafu::{ResultExt, Snafu};
5+
6+
#[derive(Debug, Default, Deserialize)]
7+
pub struct UserConfig {
8+
pub version: VersionOptions,
9+
}
10+
11+
#[derive(Debug, Deserialize)]
12+
pub struct VersionOptions {
13+
pub check_enabled: bool,
14+
}
15+
16+
#[derive(Debug, Snafu)]
17+
pub enum Error {
18+
#[snafu(display("failed to read config file from {path}", path = path.display()))]
19+
Read {
20+
source: std::io::Error,
21+
path: PathBuf,
22+
},
23+
24+
#[snafu(display("failed to deserialize config file located at {path} as TOML", path = path.display()))]
25+
Deserialize {
26+
source: toml::de::Error,
27+
path: PathBuf,
28+
},
29+
}
30+
31+
impl UserConfig {
32+
/// Reads [`UserConfig`] from `path` or if not found, falls back to the default config.
33+
pub fn from_file_or_default<P>(path: P) -> Result<Self, Error>
34+
where
35+
P: AsRef<Path>,
36+
{
37+
let path = path.as_ref();
38+
39+
match std::fs::read_to_string(path) {
40+
Ok(contents) => toml::from_str(&contents).context(DeserializeSnafu { path }),
41+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(Self::default()),
42+
Err(err) => Err(Error::Read {
43+
path: path.to_path_buf(),
44+
source: err,
45+
}),
46+
}
47+
}
48+
}
49+
50+
impl Default for VersionOptions {
51+
fn default() -> Self {
52+
Self {
53+
check_enabled: true,
54+
}
55+
}
56+
}

rust/stackablectl/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod args;
22
pub mod cli;
33
pub mod cmds;
4+
pub mod config;
45
pub mod constants;
56
pub mod output;
67
pub mod release_check;

0 commit comments

Comments
 (0)