Skip to content

Commit

Permalink
Add CLI Migrations (#805)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuajerin authored May 23, 2024
1 parent 630f05f commit 1569225
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 47 deletions.
283 changes: 248 additions & 35 deletions tembo-cli/Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tembo-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ test-case = "=2.0.0-rc2"
clap-markdown = { git = "https://github.com/tembo-io/clap-markdown.git", branch = "main", version = "0.1.3" }
url = "2.5.0"
rand = "0.8.5"
sqlx-cli = "0.7.3"

[target.aarch64-unknown-linux-musl.dependencies]
openssl = { version = "0.10", features = ["vendored"] }
Expand Down
2 changes: 2 additions & 0 deletions tembo-cli/examples/migrations-1/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
docker-compose.yml
migration-test/*
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE users
ADD COLUMN email TEXT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
INSERT INTO users (name, email)
VALUES ('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Charlie', 'charlie@example.com');
4 changes: 4 additions & 0 deletions tembo-cli/examples/migrations-1/tembo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[migration-test]
environment = "dev"
instance_name = "migration-test"
stack_type = "Standard"
65 changes: 53 additions & 12 deletions tembo-cli/src/cmd/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use itertools::Itertools;
use log::info;
use spinoff::spinners;
use spinoff::Spinner;
use sqlx::{migrate::Migrator, PgPool};
use std::fmt::Write;
use std::path::PathBuf;
use std::{
Expand Down Expand Up @@ -155,7 +156,7 @@ fn tembo_cloud_apply(
if instance_setting.stack_file.is_some() {
error("Stack File is only available for local testing.");
}
let result = tembo_cloud_apply_instance(&env, instance_setting);
let result = tembo_cloud_apply_instance(&env, instance_setting, _key.to_string());

match result {
Ok(i) => i,
Expand All @@ -181,10 +182,7 @@ fn docker_apply(

for (_key, instance_setting) in instance_settings.iter_mut() {
let final_instance_setting = docker_apply_instance(verbose, instance_setting.to_owned())?;
final_instance_settings.insert(
final_instance_setting.instance_name.clone(),
final_instance_setting,
);
final_instance_settings.insert(_key.to_string(), final_instance_setting);
}

let rendered_dockercompose: String =
Expand Down Expand Up @@ -226,14 +224,29 @@ fn docker_apply(
.bold()
));

// If all of the above was successful, we can print the url to user
instance_started(
&format!(
"postgres://postgres:postgres@{}.local.tembo.io:{}",
instance_setting.instance_name, port
),
"local",
let database_url = &format!(
"postgres://postgres:postgres@{}.local.tembo.io:{}",
instance_setting.instance_name, port
);

let migrations_dir = PathBuf::from("tembo-migrations").join(_key);

// Apply SQLx migrations if the tembo-migrations directory exists
if migrations_dir.exists() && migrations_dir.is_dir() {
let mut sp = Spinner::new(spinners::Dots, "Applying migrations", spinoff::Color::White);
tokio::runtime::Runtime::new()?
.block_on(apply_migrations(database_url.to_string(), migrations_dir))?;
sp.stop_with_message(&format!(
"{} {}",
"✓".color(colors::indicator_good()).bold(),
format!("Migrations applied for Key {}", _key)
.color(colorful::Color::White)
.bold()
));
}

// If all of the above was successful, we can print the url to user
instance_started(database_url, "local");
}
Ok(())
}
Expand Down Expand Up @@ -356,6 +369,7 @@ fn process_app_services(
pub fn tembo_cloud_apply_instance(
env: &Environment,
instance_settings: &InstanceSettings,
key: String,
) -> Result<(), anyhow::Error> {
let profile = env
.selected_profile
Expand Down Expand Up @@ -412,6 +426,15 @@ pub fn tembo_cloud_apply_instance(
));
clean_console();
let connection_string = construct_connection_string(conn_info, password);

let migrations_dir = PathBuf::from("tembo-migrations").join(key);
if migrations_dir.exists() && migrations_dir.is_dir() {
tokio::runtime::Runtime::new()?.block_on(apply_migrations(
connection_string.to_string(),
migrations_dir,
))?;
}

instance_started(
&connection_string,
&instance_settings.stack_type.clone().unwrap(),
Expand All @@ -424,6 +447,24 @@ pub fn tembo_cloud_apply_instance(
Ok(())
}

async fn apply_migrations(
database_url: String,
migrations_dir: PathBuf,
) -> Result<(), anyhow::Error> {
let pool = PgPool::connect(&database_url).await?;

for entry in fs::read_dir(&migrations_dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
let migrator = Migrator::new(migrations_dir.clone()).await?;
migrator.run(&pool).await?;
}
}

Ok(())
}

fn get_conn_info_with_creds(
profile: Profile,
instance_id: Option<String>,
Expand Down
39 changes: 39 additions & 0 deletions tembo-cli/tests/integration_tests_docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,45 @@ async fn vector() -> Result<(), anyhow::Error> {
Ok(())
}

#[tokio::test]
async fn migrations() -> Result<(), anyhow::Error> {
let root_dir = env!("CARGO_MANIFEST_DIR");
let test_dir = PathBuf::from(root_dir)
.join("examples")
.join("migrations-1");

env::set_current_dir(&test_dir)?;

// tembo init
let mut cmd = Command::cargo_bin(CARGO_BIN)?;
cmd.arg("init");
cmd.assert().success();

// tembo context set --name local
let mut cmd = Command::cargo_bin(CARGO_BIN)?;
cmd.arg("context");
cmd.arg("set");
cmd.arg("--name");
cmd.arg("local");
cmd.assert().success();

// tembo apply
let mut cmd = Command::cargo_bin(CARGO_BIN)?;
cmd.arg("--verbose");
cmd.arg("apply");
cmd.assert().success();

// tembo delete
let mut cmd = Command::cargo_bin(CARGO_BIN)?;
cmd.arg("delete");
let _ = cmd.ok();

// check can't connect
assert!(assert_can_connect("migration-test".to_str()).await.is_err());

Ok(())
}

#[tokio::test]
async fn data_warehouse() -> Result<(), anyhow::Error> {
let instance_name = "data-warehouse";
Expand Down

0 comments on commit 1569225

Please sign in to comment.