11"""Configuration management commands."""
22
3- from typing import List , Optional
3+ from typing import Optional
44
55import typer
66from rich .console import Console
7- from rich .table import Table
87
98from 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+
1211from psa .core .api import ApiClient , ApiError
12+ from psa .core .config import CONFIG_PATH , PsaConfig , get_config
1313from psa .core .output import print_error
1414
1515console = Console ()
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