init added in the postgres onboarding

This commit is contained in:
dal 2025-02-25 20:32:34 -07:00
parent 5ac7fc2dad
commit efca65932e
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
1 changed files with 193 additions and 1 deletions

View File

@ -117,12 +117,15 @@ pub async fn init(destination_path: Option<&str>) -> Result<()> {
DatabaseType::Redshift => {
setup_redshift(buster_creds.url, buster_creds.api_key, &config_path).await
}
DatabaseType::Postgres => {
setup_postgres(buster_creds.url, buster_creds.api_key, &config_path).await
}
_ => {
println!(
"{}",
format!("{} support is coming soon!", db_type).yellow()
);
println!("Currently, only Redshift is supported.");
println!("Currently, only Redshift and Postgres are supported.");
Err(anyhow::anyhow!("Database type not yet implemented"))
}
}
@ -332,6 +335,195 @@ async fn setup_redshift(
}
}
async fn setup_postgres(
buster_url: String,
buster_api_key: String,
config_path: &Path,
) -> Result<()> {
println!("{}", "Setting up PostgreSQL connection...".bold().green());
// Collect name (with validation)
let name_regex = Regex::new(r"^[a-zA-Z0-9_-]+$")?;
let name = Text::new("Enter a unique name for this data source:")
.with_help_message("Only alphanumeric characters, dash (-) and underscore (_) allowed")
.with_validator(move |input: &str| {
if input.trim().is_empty() {
return Ok(Validation::Invalid("Name cannot be empty".into()));
}
if name_regex.is_match(input) {
Ok(Validation::Valid)
} else {
Ok(Validation::Invalid(
"Name must contain only alphanumeric characters, dash (-) or underscore (_)"
.into(),
))
}
})
.prompt()?;
// Collect host
let host = Text::new("Enter the PostgreSQL host:")
.with_help_message("Example: localhost or db.example.com")
.with_validator(|input: &str| {
if input.trim().is_empty() {
return Ok(Validation::Invalid("Host cannot be empty".into()));
}
Ok(Validation::Valid)
})
.prompt()?;
// Collect port (with validation)
let port_str = Text::new("Enter the PostgreSQL port:")
.with_default("5432") // Default Postgres port is 5432
.with_help_message("Default PostgreSQL port is 5432")
.with_validator(|input: &str| match input.parse::<u16>() {
Ok(_) => Ok(Validation::Valid),
Err(_) => Ok(Validation::Invalid(
"Port must be a valid number between 1 and 65535".into(),
)),
})
.prompt()?;
let port = port_str.parse::<u16>()?;
// Collect username
let username = Text::new("Enter the PostgreSQL username:")
.with_validator(|input: &str| {
if input.trim().is_empty() {
return Ok(Validation::Invalid("Username cannot be empty".into()));
}
Ok(Validation::Valid)
})
.prompt()?;
// Collect password (masked)
let password = Password::new("Enter the PostgreSQL password:")
.with_validator(|input: &str| {
if input.trim().is_empty() {
return Ok(Validation::Invalid("Password cannot be empty".into()));
}
Ok(Validation::Valid)
})
.without_confirmation()
.prompt()?;
// Collect database (optional)
let database = Text::new("Enter the PostgreSQL database name (optional):")
.with_help_message("Leave blank to access all available databases")
.prompt()?;
let database = if database.trim().is_empty() {
None
} else {
Some(database)
};
// Collect schema (optional)
let schema = Text::new("Enter the PostgreSQL schema (optional):")
.with_help_message("Leave blank to access all available schemas")
.with_default("public") // Default Postgres schema is usually 'public'
.prompt()?;
let schema = if schema.trim().is_empty() {
None
} else {
Some(schema)
};
// Show summary and confirm
println!("\n{}", "Connection Summary:".bold());
println!("Name: {}", name.cyan());
println!("Host: {}", host.cyan());
println!("Port: {}", port.to_string().cyan());
println!("Username: {}", username.cyan());
println!("Password: {}", "********".cyan());
// Display database and schema with clear indication if they're empty
if let Some(db) = &database {
println!("Database: {}", db.cyan());
} else {
println!("Database: {}", "All databases (null)".cyan());
}
if let Some(sch) = &schema {
println!("Schema: {}", sch.cyan());
} else {
println!("Schema: {}", "All schemas (null)".cyan());
}
let confirm = Confirm::new("Do you want to create this data source?")
.with_default(true)
.prompt()?;
if !confirm {
println!("{}", "Data source creation cancelled.".yellow());
return Ok(());
}
// Create API request
let request = PostDataSourcesRequest {
name: name.clone(),
env: "dev".to_string(), // Default to dev environment
credential: Credential::Postgres(PostgresCredentials {
host,
port,
username,
password,
database: database.clone().unwrap_or_default(),
schema: schema.clone().unwrap_or_default(),
jump_host: None,
ssh_username: None,
ssh_private_key: None,
}),
};
// Send to API with progress indicator
let spinner = ProgressBar::new_spinner();
spinner.set_style(
ProgressStyle::default_spinner()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ")
.template("{spinner:.green} {msg}")
.unwrap(),
);
spinner.set_message("Sending credentials to Buster API...");
spinner.enable_steady_tick(Duration::from_millis(100));
let client = BusterClient::new(buster_url, buster_api_key)?;
match client.post_data_sources(vec![request]).await {
Ok(_) => {
spinner.finish_with_message(
"✓ Data source created successfully!"
.green()
.bold()
.to_string(),
);
println!(
"\nData source '{}' is now available for use with Buster.",
name.cyan()
);
// Create a copy of the values we need for the config file
let db_copy = database.clone();
let schema_copy = schema.clone();
// Create buster.yml file
create_buster_config_file(
config_path,
&name,
db_copy.as_deref(),
schema_copy.as_deref(),
)?;
println!("You can now use this data source with other Buster commands.");
Ok(())
}
Err(e) => {
spinner.finish_with_message("✗ Failed to create data source".red().bold().to_string());
println!("\nError: {}", e);
println!("Please check your credentials and try again.");
Err(anyhow::anyhow!("Failed to create data source: {}", e))
}
}
}
// Helper function to create buster.yml file
fn create_buster_config_file(
path: &Path,