mirror of https://github.com/buster-so/buster.git
lets test the cli release
This commit is contained in:
parent
7519e066f9
commit
2d1ded6643
|
@ -53,24 +53,6 @@ jobs:
|
|||
- name: Cache Rust dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install libpq (macOS and Linux)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
if [[ "${{ runner.os }}" == "macOS" ]]; then
|
||||
brew install libpq
|
||||
echo "PKG_CONFIG_PATH=$(brew --prefix libpq)/lib/pkgconfig" >> $GITHUB_ENV
|
||||
echo "LIBRARY_PATH=$(brew --prefix libpq)/lib" >> $GITHUB_ENV
|
||||
echo "LD_LIBRARY_PATH=$(brew --prefix libpq)/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV
|
||||
# For macOS, we might need to explicitly tell rustc where to find the library.
|
||||
# Adding common libpq paths to rustflags
|
||||
echo "RUSTFLAGS=-L $(brew --prefix libpq)/lib" >> $GITHUB_ENV
|
||||
elif [[ "${{ runner.os }}" == "Linux" ]]; then
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libpq-dev
|
||||
fi
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_CLEANUP: 1 # Recommended for CI to speed up
|
||||
|
||||
- name: Configure Cargo for optimized build
|
||||
run: |
|
||||
mkdir -p .cargo
|
||||
|
@ -115,6 +97,9 @@ jobs:
|
|||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
outputs: # Outputs for downstream jobs
|
||||
cli_version: ${{ steps.get_version.outputs.version }}
|
||||
cli_tag_name: ${{ steps.create_release.outputs.tag_name }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
@ -131,6 +116,7 @@ jobs:
|
|||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Extracted version: $VERSION"
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: v${{ steps.get_version.outputs.version }}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use crate::error::BusterError;
|
||||
use dirs;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use rust_embed::RustEmbed;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use crate::error::BusterError;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use rust_embed::RustEmbed;
|
||||
use dirs;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../../"]
|
||||
|
@ -21,8 +21,11 @@ use dirs;
|
|||
struct StaticAssets;
|
||||
|
||||
async fn setup_persistent_app_environment() -> Result<PathBuf, BusterError> {
|
||||
let home_dir = dirs::home_dir()
|
||||
.ok_or_else(|| BusterError::CommandError("Failed to get home directory. Cannot set up persistent app path.".to_string()))?;
|
||||
let home_dir = dirs::home_dir().ok_or_else(|| {
|
||||
BusterError::CommandError(
|
||||
"Failed to get home directory. Cannot set up persistent app path.".to_string(),
|
||||
)
|
||||
})?;
|
||||
let app_base_dir = home_dir.join(".buster");
|
||||
|
||||
fs::create_dir_all(&app_base_dir).map_err(|e| {
|
||||
|
@ -35,8 +38,9 @@ async fn setup_persistent_app_environment() -> Result<PathBuf, BusterError> {
|
|||
|
||||
for filename_cow in StaticAssets::iter() {
|
||||
let filename = filename_cow.as_ref();
|
||||
let asset = StaticAssets::get(filename)
|
||||
.ok_or_else(|| BusterError::CommandError(format!("Failed to get embedded asset: {}", filename)))?;
|
||||
let asset = StaticAssets::get(filename).ok_or_else(|| {
|
||||
BusterError::CommandError(format!("Failed to get embedded asset: {}", filename))
|
||||
})?;
|
||||
let target_file_path = app_base_dir.join(filename);
|
||||
|
||||
if let Some(parent) = target_file_path.parent() {
|
||||
|
@ -60,15 +64,24 @@ async fn setup_persistent_app_environment() -> Result<PathBuf, BusterError> {
|
|||
}
|
||||
|
||||
let supabase_volumes_functions_path = app_base_dir.join("supabase/volumes/functions");
|
||||
fs::create_dir_all(supabase_volumes_functions_path).map_err(|e| BusterError::CommandError(format!("Failed to create supabase/volumes/functions in persistent app dir: {}", e)))?;
|
||||
|
||||
fs::create_dir_all(supabase_volumes_functions_path).map_err(|e| {
|
||||
BusterError::CommandError(format!(
|
||||
"Failed to create supabase/volumes/functions in persistent app dir: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
let target_dotenv_path = app_base_dir.join(".env");
|
||||
|
||||
// Always use .env.example from embedded assets
|
||||
let example_env_filename = "supabase/.env.example";
|
||||
let asset = StaticAssets::get(example_env_filename)
|
||||
.ok_or_else(|| BusterError::CommandError(format!("Failed to get embedded asset: {}", example_env_filename)))?;
|
||||
|
||||
let asset = StaticAssets::get(example_env_filename).ok_or_else(|| {
|
||||
BusterError::CommandError(format!(
|
||||
"Failed to get embedded asset: {}",
|
||||
example_env_filename
|
||||
))
|
||||
})?;
|
||||
|
||||
fs::write(&target_dotenv_path, asset.data).map_err(|e| {
|
||||
BusterError::CommandError(format!(
|
||||
"Failed to write {} to {}: {}",
|
||||
|
@ -92,16 +105,29 @@ async fn setup_persistent_app_environment() -> Result<PathBuf, BusterError> {
|
|||
Ok(app_base_dir)
|
||||
}
|
||||
|
||||
async fn run_docker_compose_command(args: &[&str], operation_name: &str) -> Result<(), BusterError> {
|
||||
async fn run_docker_compose_command(
|
||||
args: &[&str],
|
||||
operation_name: &str,
|
||||
) -> Result<(), BusterError> {
|
||||
let persistent_app_dir = setup_persistent_app_environment().await?;
|
||||
|
||||
let data_db_path = persistent_app_dir.join("supabase/volumes/db/data");
|
||||
fs::create_dir_all(&data_db_path)
|
||||
.map_err(|e| BusterError::CommandError(format!("Failed to create persistent data directory at {}: {}", data_db_path.display(), e)))?;
|
||||
fs::create_dir_all(&data_db_path).map_err(|e| {
|
||||
BusterError::CommandError(format!(
|
||||
"Failed to create persistent data directory at {}: {}",
|
||||
data_db_path.display(),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
let data_storage_path = persistent_app_dir.join("supabase/volumes/storage");
|
||||
fs::create_dir_all(&data_storage_path)
|
||||
.map_err(|e| BusterError::CommandError(format!("Failed to create persistent data directory at {}: {}", data_storage_path.display(), e)))?;
|
||||
fs::create_dir_all(&data_storage_path).map_err(|e| {
|
||||
BusterError::CommandError(format!(
|
||||
"Failed to create persistent data directory at {}: {}",
|
||||
data_storage_path.display(),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
let pb = ProgressBar::new_spinner();
|
||||
pb.enable_steady_tick(Duration::from_millis(120));
|
||||
|
@ -112,7 +138,10 @@ async fn run_docker_compose_command(args: &[&str], operation_name: &str) -> Resu
|
|||
.expect("Failed to set progress bar style"),
|
||||
);
|
||||
if operation_name == "Starting" {
|
||||
pb.set_message(format!("{} Buster services... (this may take a few minutes)", operation_name));
|
||||
pb.set_message(format!(
|
||||
"{} Buster services... (this may take a few minutes)",
|
||||
operation_name
|
||||
));
|
||||
} else {
|
||||
pb.set_message(format!("{} Buster services...", operation_name));
|
||||
}
|
||||
|
@ -127,7 +156,11 @@ async fn run_docker_compose_command(args: &[&str], operation_name: &str) -> Resu
|
|||
.args(args);
|
||||
|
||||
let output = cmd.output().map_err(|e| {
|
||||
BusterError::CommandError(format!("Failed to execute docker compose {}: {}", args.join(" "), e))
|
||||
BusterError::CommandError(format!(
|
||||
"Failed to execute docker compose {}: {}",
|
||||
args.join(" "),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
if output.status.success() {
|
||||
|
@ -145,7 +178,10 @@ async fn run_docker_compose_command(args: &[&str], operation_name: &str) -> Resu
|
|||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
pb.abandon_with_message(format!("Error: docker compose {} failed. See console for details.", args.join(" ")));
|
||||
pb.abandon_with_message(format!(
|
||||
"Error: docker compose {} failed. See console for details.",
|
||||
args.join(" ")
|
||||
));
|
||||
println!("\nDocker Compose Error Details:\n{}", err_msg);
|
||||
Err(BusterError::CommandError(err_msg))
|
||||
}
|
||||
|
@ -161,15 +197,21 @@ pub async fn stop() -> Result<(), BusterError> {
|
|||
|
||||
pub async fn reset() -> Result<(), BusterError> {
|
||||
println!("WARNING: This command will stop all Buster services, attempt to remove their current images, and then restart them from scratch.");
|
||||
println!("This can lead to a complete wipe of the Buster database and any other local service data.");
|
||||
println!(
|
||||
"This can lead to a complete wipe of the Buster database and any other local service data."
|
||||
);
|
||||
println!("This action is irreversible.");
|
||||
print!("Are you sure you want to proceed with resetting? (yes/No): ");
|
||||
io::stdout().flush().map_err(|e| BusterError::CommandError(format!("Failed to flush stdout: {}", e)))?;
|
||||
print!("Are you sure you want to proceed with resetting? (y/n): ");
|
||||
io::stdout()
|
||||
.flush()
|
||||
.map_err(|e| BusterError::CommandError(format!("Failed to flush stdout: {}", e)))?;
|
||||
|
||||
let mut confirmation = String::new();
|
||||
io::stdin().read_line(&mut confirmation).map_err(|e| BusterError::CommandError(format!("Failed to read user input: {}", e)))?;
|
||||
io::stdin()
|
||||
.read_line(&mut confirmation)
|
||||
.map_err(|e| BusterError::CommandError(format!("Failed to read user input: {}", e)))?;
|
||||
|
||||
if confirmation.trim().to_lowercase() != "yes" {
|
||||
if confirmation.trim().to_lowercase() != "y" {
|
||||
println!("Reset cancelled by user.");
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -185,10 +227,12 @@ pub async fn reset() -> Result<(), BusterError> {
|
|||
.expect("Failed to set progress bar style"),
|
||||
);
|
||||
|
||||
// Step 1: Stop services
|
||||
pb.set_message("Resetting Buster services (step 1/4): Stopping services...");
|
||||
|
||||
let mut down_cmd = Command::new("docker");
|
||||
down_cmd.current_dir(&persistent_app_dir)
|
||||
down_cmd
|
||||
.current_dir(&persistent_app_dir)
|
||||
.arg("compose")
|
||||
.arg("-p")
|
||||
.arg("buster")
|
||||
|
@ -196,7 +240,9 @@ pub async fn reset() -> Result<(), BusterError> {
|
|||
.arg("docker-compose.yml")
|
||||
.arg("down");
|
||||
|
||||
let down_output = down_cmd.output().map_err(|e| BusterError::CommandError(format!("Failed to execute docker compose down: {}", e)))?;
|
||||
let down_output = down_cmd.output().map_err(|e| {
|
||||
BusterError::CommandError(format!("Failed to execute docker compose down: {}", e))
|
||||
})?;
|
||||
if !down_output.status.success() {
|
||||
let err_msg = format!(
|
||||
"docker compose down failed (status: {}). Logs:
|
||||
|
@ -215,9 +261,52 @@ Stderr:
|
|||
return Err(BusterError::CommandError(err_msg));
|
||||
}
|
||||
|
||||
pb.set_message("Resetting Buster services (step 2/4): Identifying service images...");
|
||||
// Step 2: Clear persistent data volumes
|
||||
pb.set_message("Resetting Buster services (step 2/4): Clearing persistent data volumes...");
|
||||
let db_volume_path = persistent_app_dir.join("supabase/volumes/db/data");
|
||||
let storage_volume_path = persistent_app_dir.join("supabase/volumes/storage");
|
||||
|
||||
if db_volume_path.exists() {
|
||||
fs::remove_dir_all(&db_volume_path).map_err(|e| {
|
||||
BusterError::CommandError(format!(
|
||||
"Failed to remove db volume at {}: {}",
|
||||
db_volume_path.display(),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
}
|
||||
fs::create_dir_all(&db_volume_path).map_err(|e| {
|
||||
BusterError::CommandError(format!(
|
||||
"Failed to recreate db volume at {}: {}",
|
||||
db_volume_path.display(),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
pb.println(format!("Successfully cleared and recreated database volume: {}", db_volume_path.display()));
|
||||
|
||||
if storage_volume_path.exists() {
|
||||
fs::remove_dir_all(&storage_volume_path).map_err(|e| {
|
||||
BusterError::CommandError(format!(
|
||||
"Failed to remove storage volume at {}: {}",
|
||||
storage_volume_path.display(),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
}
|
||||
fs::create_dir_all(&storage_volume_path).map_err(|e| {
|
||||
BusterError::CommandError(format!(
|
||||
"Failed to recreate storage volume at {}: {}",
|
||||
storage_volume_path.display(),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
pb.println(format!("Successfully cleared and recreated storage volume: {}", storage_volume_path.display()));
|
||||
|
||||
// Step 3: Identify service images
|
||||
pb.set_message("Resetting Buster services (step 3/4): Identifying service images...");
|
||||
let mut config_images_cmd = Command::new("docker");
|
||||
config_images_cmd.current_dir(&persistent_app_dir)
|
||||
config_images_cmd
|
||||
.current_dir(&persistent_app_dir)
|
||||
.arg("compose")
|
||||
.arg("-p")
|
||||
.arg("buster")
|
||||
|
@ -226,7 +315,12 @@ Stderr:
|
|||
.arg("config")
|
||||
.arg("--images");
|
||||
|
||||
let config_images_output = config_images_cmd.output().map_err(|e| BusterError::CommandError(format!("Failed to execute docker compose config --images: {}", e)))?;
|
||||
let config_images_output = config_images_cmd.output().map_err(|e| {
|
||||
BusterError::CommandError(format!(
|
||||
"Failed to execute docker compose config --images: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
if !config_images_output.status.success() {
|
||||
let err_msg = format!(
|
||||
"docker compose config --images failed (status: {}). Logs:
|
||||
|
@ -240,25 +334,39 @@ Stderr:
|
|||
String::from_utf8_lossy(&config_images_output.stdout),
|
||||
String::from_utf8_lossy(&config_images_output.stderr)
|
||||
);
|
||||
pb.abandon_with_message("Error: Failed to identify service images. See console for details.");
|
||||
println!("\nDocker Compose Config --images Error Details:\n{}", err_msg);
|
||||
pb.abandon_with_message(
|
||||
"Error: Failed to identify service images. See console for details.",
|
||||
);
|
||||
println!(
|
||||
"\nDocker Compose Config --images Error Details:\n{}",
|
||||
err_msg
|
||||
);
|
||||
return Err(BusterError::CommandError(err_msg));
|
||||
}
|
||||
|
||||
let image_list_str = String::from_utf8_lossy(&config_images_output.stdout);
|
||||
let image_names: Vec<&str> = image_list_str.lines().filter(|line| !line.trim().is_empty()).collect();
|
||||
let image_names: Vec<&str> = image_list_str
|
||||
.lines()
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
.collect();
|
||||
|
||||
// Step 4: Remove service images
|
||||
if image_names.is_empty() {
|
||||
pb.println("No images identified by docker-compose config --images. Skipping image removal.");
|
||||
pb.println(
|
||||
"No images identified by docker-compose config --images. Skipping image removal.",
|
||||
);
|
||||
} else {
|
||||
pb.set_message(format!("Resetting Buster services (step 3/4): Removing {} service image(s)...", image_names.len()));
|
||||
pb.set_message(format!(
|
||||
"Resetting Buster services (step 4/4): Removing {} service image(s)...",
|
||||
image_names.len()
|
||||
));
|
||||
for (index, image_name) in image_names.iter().enumerate() {
|
||||
let current_image_name = image_name.trim();
|
||||
if current_image_name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
pb.set_message(format!(
|
||||
"Resetting Buster services (step 3/4): Removing image {}/{} ('{}')...",
|
||||
"Resetting Buster services (step 4/4): Removing image {}/{} ('{}')...",
|
||||
index + 1,
|
||||
image_names.len(),
|
||||
current_image_name
|
||||
|
@ -266,48 +374,24 @@ Stderr:
|
|||
let mut rmi_cmd = Command::new("docker");
|
||||
rmi_cmd.arg("image").arg("rm").arg(current_image_name);
|
||||
|
||||
let rmi_output = rmi_cmd.output().map_err(|e| BusterError::CommandError(format!("Failed to execute docker image rm {}: {}", current_image_name, e)))?;
|
||||
|
||||
let rmi_output = rmi_cmd.output().map_err(|e| {
|
||||
BusterError::CommandError(format!(
|
||||
"Failed to execute docker image rm {}: {}",
|
||||
current_image_name, e
|
||||
))
|
||||
})?;
|
||||
|
||||
// Log warning on failure but continue, as image might not exist or be in use by other non-project containers
|
||||
if !rmi_output.status.success() {
|
||||
let rmi_stderr = String::from_utf8_lossy(&rmi_output.stderr);
|
||||
if !rmi_stderr.trim().is_empty() && !rmi_stderr.contains("No such image") { // Don't warn if image was already gone
|
||||
pb.println(format!("Warning: Could not remove image '{}'. It might be in use or already removed. Stderr: {}", current_image_name, rmi_stderr.trim()));
|
||||
if !rmi_stderr.trim().is_empty() && !rmi_stderr.contains("No such image") {
|
||||
// Don't warn if image was already gone
|
||||
pb.println(format!("Warning: Could not remove image '{}'. It might be in use or already removed. Stderr: {}", current_image_name, rmi_stderr.trim()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pb.set_message("Resetting Buster services (step 4/4): Starting services (pulling images if needed)...");
|
||||
let mut up_cmd = Command::new("docker");
|
||||
up_cmd.current_dir(&persistent_app_dir)
|
||||
.arg("compose")
|
||||
.arg("-p")
|
||||
.arg("buster")
|
||||
.arg("-f")
|
||||
.arg("docker-compose.yml")
|
||||
.arg("up")
|
||||
.arg("-d")
|
||||
.arg("--pull")
|
||||
.arg("always")
|
||||
.arg("--force-recreate")
|
||||
.arg("--remove-orphans");
|
||||
|
||||
let up_output = up_cmd.output().map_err(|e| BusterError::CommandError(format!("Failed to execute docker compose up: {}", e)))?;
|
||||
|
||||
if up_output.status.success() {
|
||||
pb.finish_with_message("Buster services reset and started successfully.");
|
||||
Ok(())
|
||||
} else {
|
||||
let err_msg = format!(
|
||||
"docker compose up failed after image purge (status: {}). Logs:\nWorking directory: {}\nStdout:\n{}\nStderr:\n{}",
|
||||
up_output.status,
|
||||
persistent_app_dir.display(),
|
||||
String::from_utf8_lossy(&up_output.stdout),
|
||||
String::from_utf8_lossy(&up_output.stderr)
|
||||
);
|
||||
pb.abandon_with_message("Error: docker compose up failed after image purge. See console for details.");
|
||||
println!("\nDocker Compose Up Error Details:\n{}", err_msg);
|
||||
Err(BusterError::CommandError(err_msg))
|
||||
}
|
||||
}
|
||||
pb.finish_with_message("Buster services stopped, volumes cleared, and images removed successfully.");
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue