misc: rename to 'pgd'
This commit is contained in:
24
CLAUDE.md
24
CLAUDE.md
@@ -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
2
Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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
3
pgd.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version = "18.1"
|
||||||
|
password = "GypnQVF1uV23DWvp"
|
||||||
|
port = 5433
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
_______ _______ __ __
|
▄▄▄▄ ▐▌
|
||||||
| || || |_| |
|
█ █ ▐▌
|
||||||
| _ || ___|| |
|
█▄▄▄▀ ▗▞▀▜▌
|
||||||
| |_| || | __ | |
|
█ ▗▄▖▝▚▄▟▌
|
||||||
| ___|| || | | |
|
▀ ▐▌ ▐▌
|
||||||
| | | |_| || _ |
|
▝▀▜▌
|
||||||
|___| |_______||__| |__|
|
▐▙▄▞▘
|
||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
16
src/state.rs
16
src/state.rs
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user