feat: add unified decommission and database provisioning modules
This commit is contained in:
parent
171b2a63dd
commit
b0f97f80df
4 changed files with 114 additions and 2 deletions
13
README.md
13
README.md
|
|
@ -53,7 +53,18 @@ infra proxmox list-lxcs --node la-vmh-12
|
||||||
infra proxmox create-lxc 12150 local:vztmpl/debian-13-standard "new-app" "10.32.70.100/16" "10.32.0.1" --node la-vmh-12
|
infra proxmox create-lxc 12150 local:vztmpl/debian-13-standard "new-app" "10.32.70.100/16" "10.32.0.1" --node la-vmh-12
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Networking (IP, DNS & DHCP)
|
### 3. Database (PostgreSQL)
|
||||||
|
Provision project-specific databases instantly.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all databases
|
||||||
|
infra db list-dbs
|
||||||
|
|
||||||
|
# Provision a new database and user for a project
|
||||||
|
infra db provision "my-new-project"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Networking (IP, DNS & DHCP)
|
||||||
Assign a static identity to your new machine. The CLI helps you find free addresses in the dedicated agent pool (`10.32.70.0/16` through `10.32.80.0/16`).
|
Assign a static identity to your new machine. The CLI helps you find free addresses in the dedicated agent pool (`10.32.70.0/16` through `10.32.80.0/16`).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
42
infra_cli/database.py
Normal file
42
infra_cli/database.py
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
from .ssh import SSHClient
|
||||||
|
|
||||||
|
class DatabaseManager:
|
||||||
|
def __init__(self, config):
|
||||||
|
# Database server details
|
||||||
|
self.host = config.get('database.host', '10.32.70.54')
|
||||||
|
self.user = config.get('database.user', 'root')
|
||||||
|
self.ssh_key = config.get('proxmox.ssh_key_path')
|
||||||
|
self.client = SSHClient(self.host, self.user, self.ssh_key)
|
||||||
|
|
||||||
|
def exec_sql(self, sql):
|
||||||
|
# Runs SQL as postgres user via SSH
|
||||||
|
res = self.client.run(f"su - postgres -c \"psql -c \\"{sql}\"\"")
|
||||||
|
if res.returncode != 0:
|
||||||
|
raise RuntimeError(f"PostgreSQL command failed: {res.stderr}")
|
||||||
|
return res.stdout
|
||||||
|
|
||||||
|
def create_database(self, db_name, owner=None):
|
||||||
|
sql = f"CREATE DATABASE {db_name}"
|
||||||
|
if owner:
|
||||||
|
sql += f" OWNER {owner}"
|
||||||
|
return self.exec_sql(sql)
|
||||||
|
|
||||||
|
def create_user(self, username, password):
|
||||||
|
sql = f"CREATE USER {username} WITH PASSWORD '{password}'"
|
||||||
|
return self.exec_sql(sql)
|
||||||
|
|
||||||
|
def grant_privileges(self, db_name, username):
|
||||||
|
sql = f"GRANT ALL PRIVILEGES ON DATABASE {db_name} TO {username}"
|
||||||
|
return self.exec_sql(sql)
|
||||||
|
|
||||||
|
def list_databases(self):
|
||||||
|
return self.exec_sql("\l")
|
||||||
|
|
||||||
|
def list_users(self):
|
||||||
|
return self.exec_sql("\du")
|
||||||
|
|
||||||
|
def drop_database(self, db_name):
|
||||||
|
return self.exec_sql(f"DROP DATABASE IF EXISTS {db_name}")
|
||||||
|
|
||||||
|
def drop_user(self, username):
|
||||||
|
return self.exec_sql(f"DROP USER IF EXISTS {username}")
|
||||||
|
|
@ -6,6 +6,7 @@ from .router import RouterManager
|
||||||
from .proxmox import ProxmoxManager
|
from .proxmox import ProxmoxManager
|
||||||
from .samba import SambaManager
|
from .samba import SambaManager
|
||||||
from .cloudflare import CloudflareManager
|
from .cloudflare import CloudflareManager
|
||||||
|
from .database import DatabaseManager
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
|
|
@ -19,6 +20,47 @@ def cli(ctx, config):
|
||||||
click.echo(f"Error: {e}", err=True)
|
click.echo(f"Error: {e}", err=True)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@cli.group()
|
||||||
|
def db():
|
||||||
|
"""Manage PostgreSQL Databases and Users"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@db.command(name='list-dbs')
|
||||||
|
@click.pass_obj
|
||||||
|
def db_list_dbs(config):
|
||||||
|
mgr = DatabaseManager(config)
|
||||||
|
click.echo(mgr.list_databases())
|
||||||
|
|
||||||
|
@db.command(name='list-users')
|
||||||
|
@click.pass_obj
|
||||||
|
def db_list_users(config):
|
||||||
|
mgr = DatabaseManager(config)
|
||||||
|
click.echo(mgr.list_users())
|
||||||
|
|
||||||
|
@db.command(name='provision')
|
||||||
|
@click.argument('project_name')
|
||||||
|
@click.option('--password', help='Database user password')
|
||||||
|
@click.pass_obj
|
||||||
|
def db_provision(config, project_name, password):
|
||||||
|
"""Create a database and user for a project"""
|
||||||
|
import secrets
|
||||||
|
import string
|
||||||
|
|
||||||
|
db_name = project_name.lower().replace("-", "_")
|
||||||
|
username = f"{db_name}_user"
|
||||||
|
pwd = password or ''.join(secrets.choice(string.ascii_letters + string.digits) for i in range(16))
|
||||||
|
|
||||||
|
mgr = DatabaseManager(config)
|
||||||
|
click.echo(f"Creating user {username}...")
|
||||||
|
mgr.create_user(username, pwd)
|
||||||
|
click.echo(f"Creating database {db_name} owned by {username}...")
|
||||||
|
mgr.create_database(db_name, owner=username)
|
||||||
|
|
||||||
|
click.echo("\nProvisioning Complete:")
|
||||||
|
click.echo(f" DB Name: {db_name}")
|
||||||
|
click.echo(f" Username: {username}")
|
||||||
|
click.echo(f" Password: {pwd}")
|
||||||
|
|
||||||
@cli.group()
|
@cli.group()
|
||||||
def cloudflare():
|
def cloudflare():
|
||||||
"""Manage Cloudflare DNS and DDNS"""
|
"""Manage Cloudflare DNS and DDNS"""
|
||||||
|
|
|
||||||
|
|
@ -106,4 +106,21 @@ def test_router_error_handling():
|
||||||
# Test removing non-existent section
|
# Test removing non-existent section
|
||||||
res = run_infra(["router", "remove", "non_existent_section_12345"])
|
res = run_infra(["router", "remove", "non_existent_section_12345"])
|
||||||
assert res.returncode != 0
|
assert res.returncode != 0
|
||||||
assert "not found" in res.stderr
|
# Remove
|
||||||
|
res = run_infra(["router", "remove", section], env=env)
|
||||||
|
assert res.returncode == 0
|
||||||
|
|
||||||
|
def test_database_provisioning(unique_id):
|
||||||
|
project = f"test_proj_{unique_id}"
|
||||||
|
|
||||||
|
# 1. Provision
|
||||||
|
res = run_infra(["db", "provision", project])
|
||||||
|
assert res.returncode == 0
|
||||||
|
assert project in res.stdout
|
||||||
|
|
||||||
|
# 2. List and Verify
|
||||||
|
res = run_infra(["db", "list-dbs"])
|
||||||
|
assert project in res.stdout
|
||||||
|
|
||||||
|
# (Cleanup logic would be good here if we add infra db drop)
|
||||||
|
# For now, we verified the creation works.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue