misc: initial implementation
This commit is contained in:
2
pgx.toml
2
pgx.toml
@@ -1,3 +1,3 @@
|
|||||||
version = "18.1"
|
version = "18.1"
|
||||||
password = "odqAsS49KNyaXjrw"
|
password = "P8t42UQ53iIza2Ic"
|
||||||
port = 5432
|
port = 5432
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
_______ _______ __ __
|
||||||
|
| || || |_| |
|
||||||
_ __ __ ___ __
|
| _ || ___|| |
|
||||||
| '_ \ / _` \ \/ /
|
| |_| || | __ | |
|
||||||
| |_) | (_| |> <
|
| ___|| || | | |
|
||||||
| .__/ \__, /_/\_\
|
| | | |_| || _ |
|
||||||
| | __/ |
|
|___| |_______||__| |__|
|
||||||
|_| |___/
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use clap::{Args, Parser, Subcommand, builder::styling};
|
use clap::{Parser, Subcommand, builder::styling};
|
||||||
|
|
||||||
const STYLES: styling::Styles = styling::Styles::styled()
|
const STYLES: styling::Styles = styling::Styles::styled()
|
||||||
.header(styling::AnsiColor::Green.on_default().bold())
|
.header(styling::AnsiColor::Green.on_default().bold())
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl Display for PostgresVersion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const PROJECT_FILENAME: &'static str = "pgx.toml";
|
const PROJECT_FILENAME: &str = "pgx.toml";
|
||||||
|
|
||||||
/// Configuration stored in pgx.toml
|
/// Configuration stored in pgx.toml
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ use bollard::{
|
|||||||
errors::Error,
|
errors::Error,
|
||||||
query_parameters::{
|
query_parameters::{
|
||||||
CreateContainerOptions, CreateImageOptions, InspectContainerOptions, ListImagesOptions,
|
CreateContainerOptions, CreateImageOptions, InspectContainerOptions, ListImagesOptions,
|
||||||
ListImagesOptionsBuilder, SearchImagesOptions, StartContainerOptions, StopContainerOptions,
|
StartContainerOptions, StopContainerOptions,
|
||||||
},
|
},
|
||||||
secret::{ContainerConfig, ContainerCreateBody, CreateImageInfo},
|
secret::{ContainerCreateBody, CreateImageInfo},
|
||||||
};
|
};
|
||||||
use futures::{Stream, StreamExt, TryStreamExt};
|
use futures::{Stream, StreamExt, TryStreamExt};
|
||||||
use indicatif::{MultiProgress, ProgressBar, ProgressState, ProgressStyle};
|
use indicatif::{MultiProgress, ProgressBar, ProgressState, ProgressStyle};
|
||||||
use miette::{Context, IntoDiagnostic, Result, diagnostic};
|
use miette::{Context, IntoDiagnostic, Result};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -38,7 +38,7 @@ const DEFAULT_POSTGRES_PORT: u16 = 5432;
|
|||||||
const PORT_SEARCH_RANGE: u16 = 100;
|
const PORT_SEARCH_RANGE: u16 = 100;
|
||||||
|
|
||||||
fn format_image(ver: &PostgresVersion) -> String {
|
fn format_image(ver: &PostgresVersion) -> String {
|
||||||
format!("{DOCKERHUB_POSTGRES}:{}", ver.to_string())
|
format!("{DOCKERHUB_POSTGRES}:{}", ver)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_available_port() -> Result<u16> {
|
fn find_available_port() -> Result<u16> {
|
||||||
@@ -60,9 +60,7 @@ fn find_available_port() -> Result<u16> {
|
|||||||
fn new_download_pb(multi: &MultiProgress, layer_id: &str) -> ProgressBar {
|
fn new_download_pb(multi: &MultiProgress, layer_id: &str) -> ProgressBar {
|
||||||
let pb = multi.add(ProgressBar::new(0));
|
let pb = multi.add(ProgressBar::new(0));
|
||||||
pb.set_style(
|
pb.set_style(
|
||||||
ProgressStyle::with_template(&format!(
|
ProgressStyle::with_template(&"{spinner:.green} [{elapsed_precise}] {msg} [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})".to_string())
|
||||||
"{{spinner:.green}} [{{elapsed_precise}}] {{msg}} [{{wide_bar:.cyan/blue}}] {{bytes}}/{{total_bytes}} ({{eta}})"
|
|
||||||
))
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_key("eta", |state: &ProgressState, w: &mut dyn Write| {
|
.with_key("eta", |state: &ProgressState, w: &mut dyn Write| {
|
||||||
write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()
|
write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()
|
||||||
@@ -399,16 +397,15 @@ const DATABASE: &str = "postgres";
|
|||||||
|
|
||||||
const PASSWORD_LENGTH: usize = 16;
|
const PASSWORD_LENGTH: usize = 16;
|
||||||
pub fn generate_password() -> String {
|
pub fn generate_password() -> String {
|
||||||
let password = (&mut rand::rng())
|
(&mut rand::rng())
|
||||||
.sample_iter(Alphanumeric)
|
.sample_iter(Alphanumeric)
|
||||||
.take(PASSWORD_LENGTH)
|
.take(PASSWORD_LENGTH)
|
||||||
.map(|b| b as char)
|
.map(|b| b as char)
|
||||||
.collect();
|
.collect()
|
||||||
password
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_RETRIES: u32 = 10;
|
const MAX_RETRIES: u32 = 10;
|
||||||
const VERIFY_DURATION_SECS: u64 = 5;
|
const VERIFY_DURATION_SECS: u64 = 10;
|
||||||
|
|
||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
pub docker: DockerController,
|
pub docker: DockerController,
|
||||||
@@ -473,7 +470,7 @@ impl Controller {
|
|||||||
let instance_state = state.get_mut(&project.name);
|
let instance_state = state.get_mut(&project.name);
|
||||||
|
|
||||||
let container_id = match instance_state {
|
let container_id = match instance_state {
|
||||||
Some(instance) => match self.ensure_container_exists(&instance).await? {
|
Some(instance) => match self.ensure_container_exists(instance).await? {
|
||||||
Some(id) => id,
|
Some(id) => id,
|
||||||
None => self.update_project_container(project, &mut state).await?,
|
None => self.update_project_container(project, &mut state).await?,
|
||||||
},
|
},
|
||||||
@@ -493,7 +490,6 @@ impl Controller {
|
|||||||
.is_container_running_by_id(&container_id)
|
.is_container_running_by_id(&container_id)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
println!("Container is already running");
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,7 +499,7 @@ impl Controller {
|
|||||||
let result = self.try_starting_container(&container_id, attempt).await;
|
let result = self.try_starting_container(&container_id, attempt).await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => break,
|
Ok(_) => return Ok(()),
|
||||||
Err(err) => println!("Error: {:#?}", err),
|
Err(err) => println!("Error: {:#?}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,11 +516,9 @@ impl Controller {
|
|||||||
container_id: &String,
|
container_id: &String,
|
||||||
attempt: u32,
|
attempt: u32,
|
||||||
) -> Result<(), miette::Error> {
|
) -> Result<(), miette::Error> {
|
||||||
Ok(
|
|
||||||
match self.docker.start_container_by_id(container_id).await {
|
match self.docker.start_container_by_id(container_id).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_secs(VERIFY_DURATION_SECS))
|
tokio::time::sleep(tokio::time::Duration::from_secs(VERIFY_DURATION_SECS)).await;
|
||||||
.await;
|
|
||||||
|
|
||||||
if self.docker.is_container_running_by_id(container_id).await? {
|
if self.docker.is_container_running_by_id(container_id).await? {
|
||||||
println!("Container started successfully and verified running");
|
println!("Container started successfully and verified running");
|
||||||
@@ -543,8 +537,8 @@ impl Controller {
|
|||||||
attempt, MAX_RETRIES, e
|
attempt, MAX_RETRIES, e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_project_container(
|
async fn update_project_container(
|
||||||
@@ -596,7 +590,7 @@ impl Controller {
|
|||||||
_container_id: &String,
|
_container_id: &String,
|
||||||
container_version: PostgresVersion,
|
container_version: PostgresVersion,
|
||||||
) -> Result<(), miette::Error> {
|
) -> Result<(), miette::Error> {
|
||||||
Ok(if container_version != project.config.version {
|
let _: () = if container_version != project.config.version {
|
||||||
let needs_upgrade = container_version < project.config.version;
|
let needs_upgrade = container_version < project.config.version;
|
||||||
|
|
||||||
if needs_upgrade {
|
if needs_upgrade {
|
||||||
@@ -627,6 +621,7 @@ impl Controller {
|
|||||||
project.config.version
|
project.config.version
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ use crate::controller::Controller;
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
println!("{}", include_str!("./banner.txt"));
|
println!("{}", include_str!("./banner.txt"));
|
||||||
let controller = Controller::new().await?;
|
|
||||||
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
init_tracing(cli.verbose);
|
init_tracing(cli.verbose);
|
||||||
|
|
||||||
info!("pgx.start");
|
info!("pgx.start");
|
||||||
|
let controller = Controller::new().await?;
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
cli::Commands::Init => controller.init_project().await?,
|
cli::Commands::Init => controller.init_project().await?,
|
||||||
@@ -33,7 +33,5 @@ async fn main() -> Result<()> {
|
|||||||
fn init_tracing(verbose: bool) {
|
fn init_tracing(verbose: bool) {
|
||||||
use tracing_subscriber::{fmt, prelude::*};
|
use tracing_subscriber::{fmt, prelude::*};
|
||||||
|
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::fmt::init();
|
||||||
.with(fmt::layer().with_target(false).with_level(true))
|
|
||||||
.init();
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user