misc: rename to 'pgd'

This commit is contained in:
hdbg
2025-12-04 18:53:39 +01:00
parent 6e2ee650d8
commit 71f363fde9
11 changed files with 55 additions and 52 deletions

View File

@@ -4,27 +4,27 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview ## Project Overview
`pgx` is a CLI tool for managing project-scoped PostgreSQL instances running in Docker containers. Each project gets its own isolated Postgres instance, managed through a `pgx.toml` configuration file. `pgd` is a CLI tool for managing project-scoped PostgreSQL instances running in Docker containers. Each project gets its own isolated Postgres instance, managed through a `pgd.toml` configuration file.
## Core Architecture ## Core Architecture
### Project-Oriented Design ### Project-Oriented Design
- Each project has a `pgx.toml` file at its root that defines the Postgres configuration - Each project has a `pgd.toml` file at its root that defines the Postgres configuration
- The project name is derived from the directory containing `pgx.toml` - The project name is derived from the directory containing `pgd.toml`
- Each project gets its own Docker container - Each project gets its own Docker container
- State is tracked separately per instance to detect configuration drift - State is tracked separately per instance to detect configuration drift
### Configuration Management ### Configuration Management
The `pgx.toml` file stores: The `pgd.toml` file stores:
- `postgres_version`: PostgreSQL version to use - `postgres_version`: PostgreSQL version to use
- `database_name`: Name of the database - `database_name`: Name of the database
- `user_name`: Database user - `user_name`: Database user
- `password`: Database password - `password`: Database password
- `port`: Host port to bind (auto-selected from available ports) - `port`: Host port to bind (auto-selected from available ports)
Values are auto-populated during `pgx init` with sensible defaults or random values where appropriate. Values are auto-populated during `pgd init` with sensible defaults or random values where appropriate.
### State Tracking ### State Tracking
@@ -36,7 +36,7 @@ The tool maintains separate state for each instance to detect configuration drif
## Key Dependencies ## Key Dependencies
- **clap** (with derive feature): CLI argument parsing and command structure - **clap** (with derive feature): CLI argument parsing and command structure
- **toml**: Parsing and serializing `pgx.toml` configuration files - **toml**: Parsing and serializing `pgd.toml` configuration files
- **bollard**: Docker daemon interaction for container lifecycle management - **bollard**: Docker daemon interaction for container lifecycle management
- **tokio** (with full feature set): Async runtime for Docker operations - **tokio** (with full feature set): Async runtime for Docker operations
- **tracing** + **tracing-subscriber**: Structured logging throughout the application - **tracing** + **tracing-subscriber**: Structured logging throughout the application
@@ -80,15 +80,15 @@ cargo fmt -- --check # Check without modifying
The CLI follows this pattern: The CLI follows this pattern:
``` ```
pgx <command> [options] pgd <command> [options]
``` ```
Key commands to implement: Key commands to implement:
- `pgx init`: Create pgx.toml with auto-populated configuration - `pgd init`: Create pgd.toml with auto-populated configuration
- `pgx start`: Start the Postgres container for current project - `pgd start`: Start the Postgres container for current project
- `pgx stop`: Stop the running container - `pgd stop`: Stop the running container
- `pgx status`: Show instance status and detect drift - `pgd status`: Show instance status and detect drift
- `pgx destroy`: Remove container and clean up - `pgd destroy`: Remove container and clean up
## Implementation Notes ## Implementation Notes

2
Cargo.lock generated
View File

@@ -1001,7 +1001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]] [[package]]
name = "pgx" name = "pgd"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bollard", "bollard",

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "pgx" name = "pgd"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"

3
pgd.toml Normal file
View File

@@ -0,0 +1,3 @@
version = "18.1"
password = "GypnQVF1uV23DWvp"
port = 5433

View File

@@ -1,7 +1,7 @@
_______ _______ __ __ ▄▄▄▄ ▐▌
| || || |_| | █ █ ▐▌
| _ || ___|| | █▄▄▄▀ ▗▞▀▜▌
| |_| || | __ | | ▗▄▖▝▚▄▟▌
| ___|| || | | | ▐▌ ▐▌
| | | |_| || _ | ▝▀▜▌
|___| |_______||__| |__| ▐▙▄▞▘

View File

@@ -7,7 +7,7 @@ const STYLES: styling::Styles = styling::Styles::styled()
.placeholder(styling::AnsiColor::Cyan.on_default()); .placeholder(styling::AnsiColor::Cyan.on_default());
#[derive(Parser)] #[derive(Parser)]
#[command(name = "pgx")] #[command(name = "pgd")]
#[command(about = "Project-scoped PostgreSQL instance manager", long_about = None)] #[command(about = "Project-scoped PostgreSQL instance manager", long_about = None)]
#[command(version)] #[command(version)]
#[command(styles = STYLES)] #[command(styles = STYLES)]

View File

@@ -35,12 +35,12 @@ impl Display for PostgresVersion {
} }
} }
const PROJECT_FILENAME: &str = "pgx.toml"; const PROJECT_FILENAME: &str = "pgd.toml";
/// Configuration stored in pgx.toml /// Configuration stored in pgd.toml
#[serde_as] #[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PgxConfig { pub struct PGDConfig {
/// PostgreSQL version to use /// PostgreSQL version to use
#[serde_as(as = "DisplayFromStr")] #[serde_as(as = "DisplayFromStr")]
pub version: PostgresVersion, pub version: PostgresVersion,
@@ -52,16 +52,16 @@ pub struct PgxConfig {
pub port: u16, pub port: u16,
} }
impl PgxConfig { impl PGDConfig {
pub fn load(path: impl AsRef<Path>) -> Result<Self> { pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref(); let path = path.as_ref();
let content = std::fs::read_to_string(path) let content = std::fs::read_to_string(path)
.into_diagnostic() .into_diagnostic()
.wrap_err_with(|| format!("Failed to read config file: {}", path.display()))?; .wrap_err_with(|| format!("Failed to read config file: {}", path.display()))?;
let config: PgxConfig = toml::from_str(&content) let config: PGDConfig = toml::from_str(&content)
.into_diagnostic() .into_diagnostic()
.wrap_err("Failed to parse pgx.toml")?; .wrap_err("Failed to parse pgd.toml")?;
Ok(config) Ok(config)
} }
@@ -85,16 +85,16 @@ pub struct Project {
/// Project name (derived from directory name) /// Project name (derived from directory name)
pub name: String, pub name: String,
/// Path to the project directory containing pgx.toml /// Path to the project directory containing pgd.toml
pub path: PathBuf, pub path: PathBuf,
pub config: PgxConfig, pub config: PGDConfig,
} }
impl Project { impl Project {
pub fn container_name(&self) -> String { pub fn container_name(&self) -> String {
let container_name = format!( let container_name = format!(
"pgx-{}-{}", "pgd-{}-{}",
self.name, self.name,
self.config.version.to_string().replace('.', "_") self.config.version.to_string().replace('.', "_")
); );
@@ -110,7 +110,7 @@ impl Project {
return Ok(None); return Ok(None);
} }
let config = PgxConfig::load(&config_path)?; let config = PGDConfig::load(&config_path)?;
let name = Self::extract_project_name(&project_path)?; let name = Self::extract_project_name(&project_path)?;
Ok(Some(Project { Ok(Some(Project {
@@ -120,7 +120,7 @@ impl Project {
})) }))
} }
pub fn new(config: PgxConfig) -> Result<Self> { pub fn new(config: PGDConfig) -> Result<Self> {
let project_path = get_project_path()?; let project_path = get_project_path()?;
let name = Self::extract_project_name(&project_path)?; let name = Self::extract_project_name(&project_path)?;
@@ -143,9 +143,9 @@ impl Project {
.ok_or_else(|| miette::miette!("Failed to extract project name from path")) .ok_or_else(|| miette::miette!("Failed to extract project name from path"))
} }
/// Get the path to the pgx.toml file /// Get the path to the pgd.toml file
pub fn config_path(&self) -> PathBuf { pub fn config_path(&self) -> PathBuf {
self.path.join("pgx.toml") self.path.join("pgd.toml")
} }
/// Save the current configuration /// Save the current configuration

View File

@@ -3,7 +3,7 @@ use miette::{bail, miette};
use miette::Result; use miette::Result;
use crate::{ use crate::{
config::{PgxConfig, PostgresVersion, Project}, config::{PGDConfig, PostgresVersion, Project},
controller::docker::DockerController, controller::docker::DockerController,
state::{InstanceState, StateManager}, state::{InstanceState, StateManager},
}; };
@@ -34,7 +34,7 @@ impl Controller {
return self.reconcile(project).await; return self.reconcile(project).await;
} }
println!("Initializing new pgx project..."); println!("Initializing new pgd project...");
let mut versions = self.docker.available_versions().await?; let mut versions = self.docker.available_versions().await?;
versions.sort(); versions.sort();
@@ -42,14 +42,14 @@ impl Controller {
.last() .last()
.ok_or(miette!("expected to have at least one version"))?; .ok_or(miette!("expected to have at least one version"))?;
let config = PgxConfig { let config = PGDConfig {
version: *latest_version, version: *latest_version,
password: utils::generate_password(), password: utils::generate_password(),
port: utils::find_available_port()?, port: utils::find_available_port()?,
}; };
let project = Project::new(config)?; let project = Project::new(config)?;
println!("Created pgx.toml in {}", project.path.display()); println!("Created pgd.toml in {}", project.path.display());
println!(" Project: {}", project.name); println!(" Project: {}", project.name);
println!(" PostgreSQL version: {}", project.config.version); println!(" PostgreSQL version: {}", project.config.version);
println!(" Port: {}", project.config.port); println!(" Port: {}", project.config.port);

View File

@@ -34,7 +34,7 @@ impl DockerController {
let docker = Docker::connect_with_local_defaults() let docker = Docker::connect_with_local_defaults()
.into_diagnostic() .into_diagnostic()
.wrap_err( .wrap_err(
"Failed to connect to Docker! pgx required Docker installed. Make sure it's running.", "Failed to connect to Docker! pgd required Docker installed. Make sure it's running.",
)?; )?;
info!("docker.created"); info!("docker.created");
@@ -156,7 +156,7 @@ impl DockerController {
}; };
let mut labels = HashMap::new(); let mut labels = HashMap::new();
labels.insert("pgx.postgres.version".to_string(), version.to_string()); labels.insert("pgd.postgres.version".to_string(), version.to_string());
let config = ContainerCreateBody { let config = ContainerCreateBody {
image: Some(image), image: Some(image),
@@ -255,8 +255,8 @@ impl DockerController {
.ok_or_else(|| miette!("Container has no labels"))?; .ok_or_else(|| miette!("Container has no labels"))?;
let version_str = labels let version_str = labels
.get("pgx.postgres.version") .get("pgd.postgres.version")
.ok_or_else(|| miette!("Container missing pgx.postgres.version label"))?; .ok_or_else(|| miette!("Container missing pgd.postgres.version label"))?;
PostgresVersion::from_str(version_str) PostgresVersion::from_str(version_str)
.map_err(|_| miette!("Invalid version in label: {}", version_str)) .map_err(|_| miette!("Invalid version in label: {}", version_str))

View File

@@ -23,7 +23,7 @@ async fn main() -> Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
init_tracing(cli.verbose); init_tracing(cli.verbose);
info!("pgx.start"); info!("pgd.start");
let controller = Controller::new().await?; let controller = Controller::new().await?;
match cli.command { match cli.command {

View File

@@ -21,7 +21,7 @@ pub struct InstanceState {
pub created_at: u64, pub created_at: u64,
} }
/// Manages the global state file at ~/.pgx/state.json /// Manages the global state file at ~/.pgd/state.json
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateManager { pub struct StateManager {
/// Map of project name to instance state /// Map of project name to instance state
@@ -29,23 +29,23 @@ pub struct StateManager {
instances: HashMap<String, InstanceState>, instances: HashMap<String, InstanceState>,
} }
/// Get the path to the state file (~/.pgx/state.json) /// Get the path to the state file (~/.pgd/state.json)
fn state_file_path() -> Result<PathBuf> { fn state_file_path() -> Result<PathBuf> {
let home = std::env::var("HOME") let home = std::env::var("HOME")
.into_diagnostic() .into_diagnostic()
.wrap_err("Failed to get HOME environment variable")?; .wrap_err("Failed to get HOME environment variable")?;
Ok(PathBuf::from(home).join(".pgx").join("state.json")) Ok(PathBuf::from(home).join(".pgd").join("state.json"))
} }
/// Get the path to the .pgx directory /// Get the path to the .pgd directory
pub fn pgx_dir() -> Result<PathBuf> { pub fn pgd_dir() -> Result<PathBuf> {
let home = std::env::var("HOME") let home = std::env::var("HOME")
.into_diagnostic() .into_diagnostic()
.wrap_err("Failed to get HOME environment variable")?; .wrap_err("Failed to get HOME environment variable")?;
Ok(PathBuf::from(home).join(".pgx")) Ok(PathBuf::from(home).join(".pgd"))
} }
impl StateManager { impl StateManager {
@@ -58,7 +58,7 @@ impl StateManager {
if let Some(parent) = state_path.parent() { if let Some(parent) = state_path.parent() {
std::fs::create_dir_all(parent) std::fs::create_dir_all(parent)
.into_diagnostic() .into_diagnostic()
.wrap_err("Failed to create .pgx directory")?; .wrap_err("Failed to create .pgd directory")?;
} }
// Return empty state // Return empty state
@@ -86,7 +86,7 @@ impl StateManager {
if let Some(parent) = state_path.parent() { if let Some(parent) = state_path.parent() {
std::fs::create_dir_all(parent) std::fs::create_dir_all(parent)
.into_diagnostic() .into_diagnostic()
.wrap_err("Failed to create .pgx directory")?; .wrap_err("Failed to create .pgd directory")?;
} }
let content = serde_json::to_string_pretty(self) let content = serde_json::to_string_pretty(self)