Skip to content

Commit 8751415

Browse files
authored
Merge pull request #45 from psadmin-io/staging
Release 0.2.2
2 parents 9db5756 + 7884930 commit 8751415

25 files changed

Lines changed: 3109 additions & 798 deletions

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "psa-cli"
3-
version = "0.2.0"
3+
version = "0.2.2"
44
description = "PeopleSoft Administration CLI"
55
readme = "README.md"
66
requires-python = ">=3.8"

src/psa/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""PeopleSoft Administration Tools."""
22

3-
__version__ = "0.2.0"
3+
__version__ = "0.2.2"

src/psa/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import typer
66

77
from psa import __version__
8-
from psa.commands import config, domain, dpk, ops
8+
from psa.commands import config, domain, dpk, kit, ops
99
from psa.core.output import Verbosity, console, set_verbosity
1010

1111
# Main application
@@ -21,6 +21,7 @@
2121
app.add_typer(config.app, name="config")
2222
app.add_typer(domain.app, name="domain")
2323
app.add_typer(dpk.app, name="dpk")
24+
app.add_typer(kit.app, name="kit")
2425
app.add_typer(ops.app, name="ops")
2526

2627

src/psa/commands/config.py

Lines changed: 29 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
"""Configuration management commands."""
22

3-
from typing import List, Optional
3+
from typing import Optional
44

55
import typer
66
from rich.console import Console
7-
from rich.table import Table
87

98
from psa.commands import init
10-
from psa.core.config import CONFIG_PATH, PsaConfig, get_config
11-
from psa.core.domain_cache import get_cached_domain_id
9+
from pathlib import Path
10+
1211
from psa.core.api import ApiClient, ApiError
12+
from psa.core.config import CONFIG_PATH, PsaConfig, get_config
1313
from psa.core.output import print_error
1414

1515
console = Console()
@@ -21,7 +21,7 @@
2121
)
2222

2323

24-
app.command(name="setup")(init.init)
24+
app.command(name="setup")(init.standalone_setup)
2525

2626

2727
@app.command(name="init", hidden=True)
@@ -93,6 +93,9 @@ def config_init(
9393
"parallel_boot": ("bool", "Use parallelboot instead of boot"),
9494
"sudo_enabled": ("bool", "Use sudo to run commands as runtime_user"),
9595
"runtime_user": ("str", "OS user for domain commands"),
96+
"dpk_repo_path": ("path", "Path to DPK file repository (PCM mount or local dir)"),
97+
"psa_kit_path": ("path", "Path to PSA Kit installation"),
98+
"psa_cust_path": ("path", "Path to PSA customer customizations"),
9699
}
97100

98101

@@ -128,6 +131,8 @@ def config_set(
128131
try:
129132
if val_type == "bool":
130133
setattr(config, key, _parse_bool(value))
134+
elif val_type == "path":
135+
setattr(config, key, Path(value))
131136
else:
132137
setattr(config, key, value)
133138
except ValueError as e:
@@ -157,8 +162,23 @@ def config_show() -> None:
157162
console.print(f" PS_CFG_HOME: {config.ps_cfg_home}")
158163
if config.ps_home:
159164
console.print(f" PS_HOME: {config.ps_home}")
165+
if config.ps_app_home:
166+
console.print(f" PS_APP_HOME: {config.ps_app_home}")
167+
if config.ps_cust_home:
168+
console.print(f" PS_CUST_HOME: {config.ps_cust_home}")
169+
if config.psa_kit_path:
170+
console.print(f" PSA Kit: {config.psa_kit_path}")
171+
if config.psa_cust_path:
172+
console.print(f" PSA Cust: {config.psa_cust_path}")
173+
if config.multi_homes:
174+
console.print(f" Multi-homes: {', '.join(str(p) for p in config.multi_homes)}")
160175
console.print(f" Runtime user: {config.runtime_user}")
161176

177+
console.print("\n[bold]Options:[/bold]")
178+
console.print(f" sudo_enabled: {config.sudo_enabled}")
179+
console.print(f" parallel_boot: {config.parallel_boot}")
180+
console.print(f" skip_domain_confirm: {config.skip_domain_confirm}")
181+
162182
if config.ops.is_configured():
163183
console.print("\n[bold]PSA-OPS:[/bold]")
164184
console.print(f" URL: {config.ops.url}")
@@ -170,125 +190,11 @@ def config_show() -> None:
170190
console.print(f" Tier: {config.ops.tier}")
171191
if config.ops.pillar:
172192
console.print(f" Pillar: {config.ops.pillar}")
193+
if config.ops.zone:
194+
console.print(f" Zone: {config.ops.zone}")
195+
if config.ops.suppress_fact_warnings:
196+
console.print(f" Suppress fact warnings: {config.ops.suppress_fact_warnings}")
173197
if config.ops.ps_role:
174198
console.print(f" Role: {config.ops.ps_role}")
175199
else:
176200
console.print("\n[yellow]PSA-OPS not configured[/yellow]")
177-
178-
179-
def _resolve_domain_id(client: ApiClient, name: str) -> str:
180-
"""Resolve domain name to UUID. Checks local cache first, then PSA-OPS."""
181-
cached = get_cached_domain_id(name)
182-
if cached:
183-
return cached
184-
domain = client.resolve_domain(name)
185-
if not domain:
186-
print_error(f"Domain '{name}' not found in PSA-OPS")
187-
raise typer.Exit(1)
188-
return domain["id"]
189-
190-
191-
@app.command(name="compare")
192-
def config_compare(
193-
domains: List[str] = typer.Argument(
194-
...,
195-
help="Domain names to compare (at least 2)",
196-
),
197-
config_type: Optional[str] = typer.Option(
198-
None,
199-
"--type",
200-
"-t",
201-
help="Config file type (e.g. psappsrv.cfg, psprcs.cfg)",
202-
),
203-
show_all: bool = typer.Option(
204-
False,
205-
"--all",
206-
"-a",
207-
help="Show all rows including identical values",
208-
),
209-
json_output: bool = typer.Option(
210-
False,
211-
"--json",
212-
"-j",
213-
help="Output as JSON",
214-
),
215-
) -> None:
216-
"""
217-
Compare config across domains
218-
219-
Fetches config comparison from PSA-OPS for 2+ domains and displays
220-
differences. By default only shows 'different' and 'missing' rows.
221-
222-
Examples:
223-
psa config compare APPDOM1 APPDOM2
224-
psa config compare APPDOM1 APPDOM2 --type psappsrv.cfg
225-
psa config compare APPDOM1 APPDOM2 APPDOM3 --all
226-
"""
227-
if len(domains) < 2:
228-
print_error("At least 2 domain names required for comparison")
229-
raise typer.Exit(1)
230-
231-
config = get_config()
232-
if not config.ops.is_configured():
233-
print_error("PSA-OPS not configured. Run 'psa config setup' first")
234-
raise typer.Exit(1)
235-
236-
client = ApiClient(config.ops.url)
237-
238-
# Resolve names to UUIDs
239-
domain_ids = []
240-
for name in domains:
241-
domain_id = _resolve_domain_id(client, name)
242-
domain_ids.append(domain_id)
243-
244-
try:
245-
result = client.compare_configs(domain_ids, config_type)
246-
except ApiError as e:
247-
print_error(f"Compare failed: {e}")
248-
raise typer.Exit(1)
249-
250-
if json_output:
251-
import json
252-
console.print_json(json.dumps(result, default=str, indent=2))
253-
return
254-
255-
rows = result.get("rows", [])
256-
if not rows:
257-
console.print("[dim]No config data to compare[/dim]")
258-
return
259-
260-
# Filter rows unless --all
261-
if not show_all:
262-
rows = [r for r in rows if r.get("status") != "same"]
263-
264-
# Count summary
265-
all_rows = result.get("rows", [])
266-
same_count = sum(1 for r in all_rows if r.get("status") == "same")
267-
diff_count = sum(1 for r in all_rows if r.get("status") == "different")
268-
missing_count = sum(1 for r in all_rows if r.get("status") == "missing")
269-
270-
# Build table
271-
table = Table(title="Config Comparison")
272-
table.add_column("Key", style="cyan")
273-
for name in domains:
274-
table.add_column(name, style="white")
275-
table.add_column("Status", style="dim")
276-
277-
for row in rows:
278-
status = row.get("status", "")
279-
values = row.get("values", {})
280-
style = ""
281-
if status == "different":
282-
style = "yellow"
283-
elif status == "missing":
284-
style = "red"
285-
286-
cells = [row.get("key", "")]
287-
for name in domains:
288-
val = values.get(name, "")
289-
cells.append(str(val) if val is not None else "[dim]-[/dim]")
290-
cells.append(f"[{style}]{status}[/{style}]" if style else status)
291-
table.add_row(*cells)
292-
293-
console.print(table)
294-
console.print(f"\n{same_count} same, {diff_count} different, {missing_count} missing")

0 commit comments

Comments
 (0)