Skip to content

Commit

Permalink
implements overlay-configs options for tembo apply (#466)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuajerin authored Jan 10, 2024
1 parent 8366d80 commit acccc03
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 38 deletions.
12 changes: 12 additions & 0 deletions tembo-cli/src/cli/tembo_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ pub struct InstanceSettings {
pub extra_domains_rw: Option<Vec<String>>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct OverlayInstanceSettings {
pub cpu: Option<String>,
pub memory: Option<String>,
pub storage: Option<String>,
pub replicas: Option<i32>,
pub stack_type: Option<String>,
pub postgres_configurations: Option<HashMap<String, Value>>,
pub extensions: Option<HashMap<String, Extension>>,
pub extra_domains_rw: Option<Vec<String>>,
}

// If a trunk project name is not specified, then assume
// it's the same name as the extension.
fn deserialize_extensions<'de, D>(
Expand Down
92 changes: 65 additions & 27 deletions tembo-cli/src/cmd/apply.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::Context as AnyhowContext;
use anyhow::Error;
use clap::Args;
use colorful::{Color, Colorful};
use colorful::Colorful;
use controller::stacks::get_stack;
use controller::stacks::types::StackType as ControllerStackType;
use log::info;
Expand Down Expand Up @@ -29,7 +30,8 @@ use crate::cli::file_utils::FileUtils;
use crate::cli::sqlx_utils::SqlxUtils;
use crate::cli::tembo_config;
use crate::cli::tembo_config::InstanceSettings;
use crate::tui::{indent, instance_started};
use crate::cli::tembo_config::OverlayInstanceSettings;
use crate::tui::instance_started;
use crate::{
cli::context::{get_current_context, Environment, Profile, Target},
tui::{clean_console, colors, white_confirmation},
Expand All @@ -41,28 +43,31 @@ const POSTGRESCONF_NAME: &str = "postgres.conf";

/// Deploys a tembo.toml file
#[derive(Args)]
pub struct ApplyCommand {}
pub struct ApplyCommand {
#[clap(long, short = 'm')]
pub merge: Option<String>,
}

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

let env = get_current_context()?;

if env.target == Target::Docker.to_string() {
return execute_docker(verbose);
return execute_docker(verbose, _merge_path);
} else if env.target == Target::TemboCloud.to_string() {
return execute_tembo_cloud(env.clone());
return execute_tembo_cloud(env.clone(), _merge_path);
}

Ok(())
}

fn execute_docker(verbose: bool) -> Result<(), anyhow::Error> {
fn execute_docker(verbose: bool, _merge_path: Option<String>) -> Result<(), anyhow::Error> {
Docker::installed_and_running()?;

let instance_settings: HashMap<String, InstanceSettings> = get_instance_settings()?;
let instance_settings = get_instance_settings(_merge_path)?;
let rendered_dockerfile: String = get_rendered_dockerfile(instance_settings.clone())?;

FileUtils::create_file(
Expand Down Expand Up @@ -111,13 +116,17 @@ fn execute_docker(verbose: bool) -> Result<(), anyhow::Error> {
&value.stack_type,
"local",
);
println!("Instance settings: {:?}", instance_settings);
}

Ok(())
}

pub fn execute_tembo_cloud(env: Environment) -> Result<(), anyhow::Error> {
let instance_settings: HashMap<String, InstanceSettings> = get_instance_settings()?;
pub fn execute_tembo_cloud(
env: Environment,
_merge_path: Option<String>,
) -> Result<(), anyhow::Error> {
let instance_settings = get_instance_settings(_merge_path)?;

let profile = env.clone().selected_profile.unwrap();
let config = Configuration {
Expand Down Expand Up @@ -434,25 +443,55 @@ fn get_trunk_installs(
vec_trunk_installs
}

pub fn get_instance_settings() -> Result<HashMap<String, InstanceSettings>, anyhow::Error> {
let mut file_path = FileUtils::get_current_working_dir();
file_path.push_str("/tembo.toml");

let contents = match fs::read_to_string(file_path.clone()) {
Ok(c) => c,
Err(e) => {
panic!("Couldn't read context file {}: {}", file_path, e);
}
};
fn merge_settings(base: &InstanceSettings, overlay: OverlayInstanceSettings) -> InstanceSettings {
InstanceSettings {
environment: base.environment.clone(), // Retain the base environment
instance_name: base.instance_name.clone(), // Retain the base instance_name
cpu: overlay.cpu.unwrap_or_else(|| base.cpu.clone()),
memory: overlay.memory.unwrap_or_else(|| base.memory.clone()),
storage: overlay.storage.unwrap_or_else(|| base.storage.clone()),
replicas: overlay.replicas.unwrap_or(base.replicas),
stack_type: overlay
.stack_type
.unwrap_or_else(|| base.stack_type.clone()),
postgres_configurations: overlay
.postgres_configurations
.or_else(|| base.postgres_configurations.clone()),
extensions: overlay.extensions.or_else(|| base.extensions.clone()),
extra_domains_rw: overlay
.extra_domains_rw
.or_else(|| base.extra_domains_rw.clone()),
}
}

let instance_settings: HashMap<String, InstanceSettings> = match toml::from_str(&contents) {
Ok(d) => d,
Err(e) => {
panic!("Unable to load data. Error: `{}`", e);
pub fn get_instance_settings(
overlay_file_path: 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();

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);
}
}
};
}

Ok(instance_settings)
Ok(final_settings)
}

pub fn get_rendered_dockerfile(
Expand Down Expand Up @@ -557,7 +596,6 @@ fn get_postgres_config(instance_settings: HashMap<String, InstanceSettings>) ->
}
}
}

postgres_config
}

Expand Down
2 changes: 1 addition & 1 deletion tembo-cli/src/cmd/context/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub fn execute() -> Result<(), anyhow::Error> {
org_id = env_org;
}

if e.target == String::from("docker") {
if e.target == *"docker" {
profile = String::from("local")
} else if let Some(env_profile) = e.profile {
profile = env_profile;
Expand Down
10 changes: 4 additions & 6 deletions tembo-cli/src/cmd/delete.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use std::collections::HashMap;

use crate::cli::context::{get_current_context, Environment, Target};
use crate::cli::docker::Docker;
use crate::cli::tembo_config::InstanceSettings;
use crate::tui::{confirmation, label};

use crate::tui::confirmation;
use clap::Args;
use core::result::Result::Ok;
use temboclient::apis::{configuration::Configuration, instance_api::delete_instance};
Expand All @@ -18,7 +16,7 @@ pub struct DeleteCommand {}
pub fn execute() -> Result<(), anyhow::Error> {
let env = get_current_context()?;

let instance_settings: HashMap<String, InstanceSettings> = get_instance_settings()?;
let instance_settings = get_instance_settings(None)?;

if env.target == Target::Docker.to_string() {
for (_key, value) in instance_settings.iter() {
Expand All @@ -32,7 +30,7 @@ pub fn execute() -> Result<(), anyhow::Error> {
}

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

let profile = env.clone().selected_profile.unwrap();
let config = Configuration {
Expand Down
1 change: 0 additions & 1 deletion tembo-cli/src/cmd/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::cli::context::{
use crate::cli::file_utils::FileUtils;
use crate::tui::confirmation;
use clap::Args;
use colorful::Colorful;

/// Initializes a local environment. Creates a sample context and configuration files.
#[derive(Args)]
Expand Down
88 changes: 87 additions & 1 deletion tembo-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ fn main() -> Result<(), anyhow::Error> {
init::execute()?;
}
SubCommands::Apply(_apply_cmd) => {
apply::execute(app.global_opts.verbose)?;
apply::execute(app.global_opts.verbose, _apply_cmd.merge.clone())?;
}
SubCommands::Validate(_validate_cmd) => {
validate::execute(app.global_opts.verbose)?;
Expand All @@ -65,3 +65,89 @@ fn main() -> Result<(), anyhow::Error> {

Ok(())
}

#[cfg(test)]
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 default_instance_settings() -> Result<(), Box<dyn std::error::Error>> {
std::env::set_current_dir(
PathBuf::from(root_dir)
.join("tests")
.join("tomls")
.join("merge"),
)?;

// Path to the overlay.toml file
let overlay_config_path = PathBuf::from(root_dir)
.join("tests")
.join("tomls")
.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 _output = Command::new(CARGO_BIN_PATH)
.arg("apply")
.arg("--merge")
.arg(overlay_config_str);

let merged_settings = apply::get_instance_settings(Some(overlay_config_str.to_string()))?;
if let Some(setting) = merged_settings.get("defaults") {
assert_ne!(setting.cpu, "0.25", "Default setting was overwritten");
} else {
return Err("Setting key 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("tests")
.join("tomls")
.join("merge"),
)?;

// Path to the overlay.toml file
let overlay_config_path = PathBuf::from(root_dir)
.join("tests")
.join("tomls")
.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 _output = Command::new(CARGO_BIN_PATH)
.arg("apply")
.arg("--merge")
.arg(overlay_config_str);

let merged_settings = apply::get_instance_settings(Some(overlay_config_str.to_string()))?;
if let Some(setting) = merged_settings.get("defaults") {
assert_eq!(setting.memory, "10Gi", "Base settings was not overwritten");
} else {
return Err("Setting key not found".into());
}

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

Ok(())
}
}
4 changes: 2 additions & 2 deletions tembo-cli/src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ pub mod colors {
ColorfulRgb::new(255, 244, 228)
}

pub fn gradient_p<'a>(log: &'a str) -> GradientDisplay<'a, [RGB; 4]> {
pub fn gradient_p(log: &str) -> GradientDisplay<'_, [RGB; 4]> {
GradientStr::gradient(
log,
[
Expand All @@ -112,7 +112,7 @@ pub mod colors {
)
}

pub fn gradient_rainbow<'a>(log: &'a str) -> GradientDisplay<'a, [RGB; 3]> {
pub fn gradient_rainbow(log: &str) -> GradientDisplay<'_, [RGB; 3]> {
GradientStr::gradient(
log,
[
Expand Down
3 changes: 3 additions & 0 deletions tembo-cli/tests/tomls/merge/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
postgres.conf
1_extensions.sql
Dockerfile
4 changes: 4 additions & 0 deletions tembo-cli/tests/tomls/merge/overlay.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[defaults]
environment = "prod"
instance_name = "overlay_instance"
memory = "10Gi"
7 changes: 7 additions & 0 deletions tembo-cli/tests/tomls/merge/tembo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[defaults]
environment = "dev"
instance_name = "defaults_instance"
cpu = "1"
storage = "50Gi"
replicas = 1
stack_type = "Standard"

0 comments on commit acccc03

Please sign in to comment.