Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

--set #485

Merged
merged 13 commits into from
Jan 22, 2024
4 changes: 3 additions & 1 deletion tembo-cli/examples/merge/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
postgres.conf
1_extensions.sql
Dockerfile
Dockerfile
merge/*
docker-compose.yml
4 changes: 2 additions & 2 deletions tembo-cli/examples/merge/overlay.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[merge]
environment = "prod"
environment = "local"
instance_name = "overlay_instance"
memory = "10Gi"
replicas = 2
2 changes: 1 addition & 1 deletion tembo-cli/examples/merge/tembo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
environment = "dev"
instance_name = "merge"
cpu = "1"
storage = "50Gi"
storage = "10Gi"
replicas = 1
stack_type = "Standard"
5 changes: 5 additions & 0 deletions tembo-cli/examples/set/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
postgres.conf
1_extensions.sql
Dockerfile
set/*
docker-compose.yml
9 changes: 9 additions & 0 deletions tembo-cli/examples/set/tembo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[set]
environment = "dev"
instance_name = "set"
cpu = "1"
memory = "2Gi"
storage = "10Gi"
replicas = 1
stack_type = "Standard"

164 changes: 106 additions & 58 deletions tembo-cli/src/cmd/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,22 @@ const POSTGRESCONF_NAME: &str = "postgres.conf";
pub struct ApplyCommand {
#[clap(long, short = 'm')]
pub merge: Option<String>,
#[clap(long, short = 's')]
pub set: Option<String>,
}

pub fn execute(verbose: bool, merge_path: Option<String>) -> Result<(), anyhow::Error> {
pub fn execute(
verbose: bool,
merge_path: Option<String>,
set_arg: Option<String>,
) -> Result<(), anyhow::Error> {
info!("Running validation!");
super::validate::execute(verbose)?;
info!("Validation completed!");

let env = get_current_context()?;

let instance_settings = get_instance_settings(merge_path)?;
let instance_settings = get_instance_settings(merge_path, set_arg)?;

if env.target == Target::Docker.to_string() {
return docker_apply(verbose, instance_settings);
Expand All @@ -68,6 +74,26 @@ pub fn execute(verbose: bool, merge_path: Option<String>) -> Result<(), anyhow::
Ok(())
}

fn parse_set_arg(set_arg: &str) -> Result<(String, String, String), Error> {
let parts: Vec<&str> = set_arg.split('=').collect();
if parts.len() != 2 {
println!("Error: Invalid format (missing '=')");
return Err(Error::msg("Invalid format for --set"));
}

let key_parts: Vec<&str> = parts[0].split('.').collect();
if key_parts.len() != 2 {
println!("Error: Invalid format (missing '.')");
return Err(Error::msg("Invalid format for --set"));
}

let instance_name = key_parts[0].to_string();
let setting_name = key_parts[1].to_string();
let setting_value = parts[1].to_string();

Ok((instance_name, setting_name, setting_value))
}

fn tembo_cloud_apply(
env: Environment,
instance_settings: HashMap<String, InstanceSettings>,
Expand Down Expand Up @@ -541,34 +567,77 @@ fn merge_settings(base: &InstanceSettings, overlay: OverlayInstanceSettings) ->
}
}

pub fn merge_instance_settings(
base_settings: &HashMap<String, InstanceSettings>,
overlay_file_path: &str,
) -> Result<HashMap<String, InstanceSettings>, Error> {
let overlay_contents = fs::read_to_string(overlay_file_path)
.with_context(|| format!("Couldn't read overlay file {}", overlay_file_path))?;
let overlay_settings: HashMap<String, OverlayInstanceSettings> =
toml::from_str(&overlay_contents).context("Unable to load data from the overlay config")?;

let mut final_settings = base_settings.clone();
for (key, overlay_value) in overlay_settings {
if let Some(base_value) = base_settings.get(&key) {
let merged_value = merge_settings(base_value, overlay_value);
final_settings.insert(key, merged_value);
}
}

Ok(final_settings)
}

pub fn set_instance_settings(
base_settings: &mut HashMap<String, InstanceSettings>,
set_arg: &str,
) -> Result<(), Error> {
let (instance_name, setting_name, setting_value) = parse_set_arg(set_arg)?;

if let Some(settings) = base_settings.get_mut(&instance_name) {
match setting_name.as_str() {
"instance_name" => settings.instance_name = setting_value,
"cpu" => settings.cpu = setting_value,
"memory" => settings.memory = setting_value,
"storage" => settings.storage = setting_value,
"replicas" => {
settings.replicas = setting_value
.parse()
.map_err(|_| Error::msg("Invalid value for replicas"))?;
}
"stack_type" => settings.stack_type = setting_value,
_ => {
return Err(Error::msg(format!("Unknown setting: {}", setting_name)));
}
}
} else {
return Err(Error::msg("Instance not found"));
}

Ok(())
}

pub fn get_instance_settings(
overlay_file_path: Option<String>,
set_arg: Option<String>,
) -> Result<HashMap<String, InstanceSettings>, Error> {
let mut base_path = FileUtils::get_current_working_dir();
base_path.push_str("/tembo.toml");
let base_contents = fs::read_to_string(&base_path)
.with_context(|| format!("Couldn't read base file {}", base_path))?;
let base_settings: HashMap<String, InstanceSettings> =
toml::from_str(&base_contents).context("Unable to load data from the base config")?;

let mut final_settings = base_settings.clone();
let mut base_settings: HashMap<String, InstanceSettings> =
toml::from_str(&base_contents).context("Unable to load data from the base config")?;

if let Some(overlay_path) = overlay_file_path {
let overlay_contents = fs::read_to_string(&overlay_path)
.with_context(|| format!("Couldn't read overlay file {}", overlay_path))?;
let overlay_settings: HashMap<String, OverlayInstanceSettings> =
toml::from_str(&overlay_contents)
.context("Unable to load data from the overlay config")?;

for (key, overlay_value) in overlay_settings {
if let Some(base_value) = base_settings.get(&key) {
let merged_value = merge_settings(base_value, overlay_value);
final_settings.insert(key, merged_value);
}
}
let overlay_settings = merge_instance_settings(&base_settings, &overlay_path)?;
base_settings.extend(overlay_settings);
}

Ok(final_settings)
if let Some(set_arg_str) = set_arg {
set_instance_settings(&mut base_settings, &set_arg_str)?;
}

Ok(base_settings)
}

pub fn get_rendered_dockerfile(
Expand Down Expand Up @@ -702,72 +771,51 @@ fn construct_connection_string(info: ConnectionInfo) -> String {
mod tests {
use super::*;
use std::path::PathBuf;
use std::process::Command;

const CARGO_BIN_PATH: &str = "cargo run ";
const ROOT_DIR: &str = env!("CARGO_MANIFEST_DIR");

#[tokio::test]
async fn merge_settings() -> Result<(), Box<dyn std::error::Error>> {
std::env::set_current_dir(PathBuf::from(ROOT_DIR).join("examples").join("merge"))?;

// Path to the overlay.toml file
let overlay_config_path = PathBuf::from(ROOT_DIR)
.join("examples")
.join("merge")
.join("overlay.toml");
let overlay_config_str = overlay_config_path.to_str().ok_or("Invalid path")?;
let overlay_config_str = overlay_config_path.to_str().unwrap();

// Running `tembo init`
let _output = Command::new(CARGO_BIN_PATH).arg("init");

let _output = Command::new(CARGO_BIN_PATH)
.arg("apply")
.arg("--merge")
.arg(overlay_config_str);

let merged_settings = get_instance_settings(Some(overlay_config_str.to_string()))?;
let merged_settings = get_instance_settings(Some(overlay_config_str.to_string()), None)?;
if let Some(setting) = merged_settings.get("merge") {
assert_ne!(setting.cpu, "0.25", "Default setting was overwritten");
assert_ne!(
setting.cpu, "0.25",
"Default CPU setting was not overwritten"
);
assert_eq!(setting.replicas, 2, "Overlay Settings are not overwritten");
assert_eq!(setting.storage, "10Gi", "Base Settings are not overwritten");
} else {
return Err("Setting key not found".into());
return Err("Merged setting key 'merge' not found".into());
}

// Running `tembo delete`
let _output = Command::new(CARGO_BIN_PATH).arg("delete");

Ok(())
}

#[tokio::test]
async fn merge() -> Result<(), Box<dyn std::error::Error>> {
std::env::set_current_dir(PathBuf::from(ROOT_DIR).join("examples").join("merge"))?;
async fn set_settings() -> Result<(), Box<dyn std::error::Error>> {
std::env::set_current_dir(PathBuf::from(ROOT_DIR).join("examples").join("set"))?;

// Path to the overlay.toml file
let overlay_config_path = PathBuf::from(ROOT_DIR)
.join("examples")
.join("merge")
.join("overlay.toml");
let overlay_config_str = overlay_config_path.to_str().ok_or("Invalid path")?;

// Running `tembo init`
let _output = Command::new(CARGO_BIN_PATH).arg("init");
let set_arg = "set.memory=2Gi";

let _output = Command::new(CARGO_BIN_PATH)
.arg("apply")
.arg("--merge")
.arg(overlay_config_str);
let final_settings = get_instance_settings(None, Some(set_arg.to_string()))?;

let merged_settings = get_instance_settings(Some(overlay_config_str.to_string()))?;
if let Some(setting) = merged_settings.get("merge") {
assert_eq!(setting.memory, "10Gi", "Base settings was not overwritten");
if let Some(setting) = final_settings.get("set") {
assert_eq!(
setting.memory, "2Gi",
"Memory setting was not correctly applied"
);
} else {
return Err("Setting key not found".into());
return Err("Setting key 'defaults' not found".into());
}

// Running `tembo delete`
let _output = Command::new(CARGO_BIN_PATH).arg("delete");

Ok(())
}
}
2 changes: 1 addition & 1 deletion tembo-cli/src/cmd/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn execute() -> Result<(), anyhow::Error> {
}

fn execute_tembo_cloud(env: Environment) -> Result<(), anyhow::Error> {
let instance_settings = get_instance_settings(None)?;
let instance_settings = get_instance_settings(None, None)?;

let profile = env.clone().selected_profile.unwrap();
let config = Configuration {
Expand Down
6 changes: 5 additions & 1 deletion tembo-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ fn main() -> Result<(), anyhow::Error> {
init::execute()?;
}
SubCommands::Apply(_apply_cmd) => {
apply::execute(app.global_opts.verbose, _apply_cmd.merge.clone())?;
apply::execute(
app.global_opts.verbose,
_apply_cmd.merge.clone(),
_apply_cmd.set.clone(),
)?;
}
SubCommands::Validate(_validate_cmd) => {
validate::execute(app.global_opts.verbose)?;
Expand Down
Loading