@@ -1502,23 +1502,157 @@ def __setitem__(self, name, value):
15021502
15031503DEFAULTS = Defaults ()
15041504
1505+ def _ini_default_from_field (field ):
1506+ """
1507+ Compute a reasonable default-as-string for inclusion in comments.
1508+ """
1509+ from .util .models import FromParent
15051510
1506- new_conf_template = """
1507- # see oscrc(5) man page for the full list of available options
1511+ if field . default is None :
1512+ return None
15081513
1509- [general]
1514+ if getattr (field , "default_is_lazy" , False ):
1515+ return None
15101516
1511- # Default URL to the API server.
1512- # Credentials and other `apiurl` specific settings must be configured in a `[$apiurl]` config section.
1513- apiurl=%(apiurl)s
1517+ ini_type = field . extra . get ( "ini_type" , None )
1518+ if ini_type :
1519+ return None
15141520
1515- [%(apiurl)s]
1516- # aliases=
1517- # user=
1518- # pass=
1519- # credentials_mgr_class=osc.credentials...
1520- """
1521+ if isinstance (field .default , FromParent ):
1522+ return None
1523+
1524+ origin_type = field .origin_type
1525+
1526+ if origin_type == bool :
1527+ return "1" if field .default else "0"
1528+
1529+ if origin_type == int :
1530+ return str (field .default )
1531+
1532+ if origin_type == list :
1533+ if not field .default :
1534+ return None
1535+ return " " .join (str (v ) for v in field .default )
1536+
1537+ if origin_type == str :
1538+ return field .default
1539+
1540+ return None
1541+
1542+
1543+ def _emit_option_block (
1544+ lines ,
1545+ ini_key : str ,
1546+ desc : str ,
1547+ default_str : str | None ,
1548+ apiurl_placeholder : str | None = None ,
1549+ ):
1550+ """
1551+ Append a fully formatted option block to `lines`.
1552+
1553+ If `apiurl_placeholder` is not None and ini_key == "apiurl",
1554+ emit an active `apiurl=...` line instead of a commented placeholder.
1555+ """
1556+ lines .append ("#" )
1557+
1558+ if desc :
1559+ for line in desc .splitlines ():
1560+ if line .strip ():
1561+ lines .append ("#--# " + line )
1562+ else :
1563+ lines .append ("#--#" )
1564+ lines .append ("#--#" )
1565+
1566+ if apiurl_placeholder is not None and ini_key == "apiurl" :
1567+ lines .append (f"apiurl={ apiurl_placeholder } " )
1568+ return
1569+
1570+ if default_str is not None :
1571+ lines .append (f"#--# Default: { ini_key } = { default_str } " )
1572+ lines .append ("#" )
1573+ lines .append (f"{ ini_key } = { default_str } " )
1574+ else :
1575+ lines .append ("#" )
1576+ lines .append (f"# { ini_key } =" )
1577+
1578+
1579+ def generate_conf_template (apiurl_placeholder : str = "%(apiurl)s" ) -> str :
1580+ """
1581+ Generate a full oscrc template.
1582+ """
1583+ line_length = 30
1584+ lines : list [str ] = []
1585+ lines .append ("# see oscrc(5) man page for the full list of available options" )
1586+ lines .append ("#" )
1587+ lines .append ("[general]" )
1588+
1589+ for name , field in Options .__fields__ .items ():
1590+ extra = field .extra
1591+
1592+ if extra .get ("section" , False ):
1593+ lines .append ("#" )
1594+ lines .append ("#" + "-" * line_length )
1595+ lines .append (f"#-- { field .default } --" )
1596+ lines .append ("#" + "-" * line_length )
1597+ continue
1598+
1599+ if extra .get ("ini_exclude" , False ) or field .exclude :
1600+ continue
1601+
1602+ if field .description is None and not extra .get ("ini_description" , None ):
1603+ continue
1604+
1605+ ini_key = extra .get ("ini_key" , name )
1606+ desc = extra .get ("ini_description" , None ) or field .description or ""
1607+ default_str = _ini_default_from_field (field )
1608+
1609+ _emit_option_block (
1610+ lines ,
1611+ ini_key = ini_key ,
1612+ desc = desc ,
1613+ default_str = default_str ,
1614+ apiurl_placeholder = apiurl_placeholder ,
1615+ )
1616+
1617+ lines .append ("#" )
1618+ lines .append (f"[{ apiurl_placeholder } ]" )
1619+ lines .append ("# Per-API server configuration" )
1620+ lines .append ("# (credentials and API specific options)" )
1621+ lines .append ("#" )
1622+
1623+ for name , field in HostOptions .__fields__ .items ():
1624+ extra = field .extra
1625+
1626+ if extra .get ("section" , False ):
1627+ continue
15211628
1629+ if extra .get ("ini_exclude" , False ) or field .exclude :
1630+ continue
1631+
1632+ if field .description is None and not extra .get ("ini_description" , None ):
1633+ continue
1634+
1635+ ini_key = extra .get ("ini_key" , name )
1636+
1637+ # apiurl itself is expressed by the section header; do not emit as key
1638+ if ini_key == "apiurl" :
1639+ continue
1640+
1641+ desc = extra .get ("ini_description" , None ) or field .description or ""
1642+ default_str = _ini_default_from_field (field )
1643+
1644+ _emit_option_block (
1645+ lines ,
1646+ ini_key = ini_key ,
1647+ desc = desc ,
1648+ default_str = default_str ,
1649+ apiurl_placeholder = None ,
1650+ )
1651+
1652+ return "\n " .join (lines ) + "\n "
1653+
1654+
1655+ new_conf_template = generate_conf_template (apiurl_placeholder = "%(apiurl)s" )
15221656
15231657account_not_configured_text = """
15241658Your user account / password are not configured yet.
@@ -1766,25 +1900,66 @@ def _extract_user_compat(cp, section, creds_mgr):
17661900 user = creds_mgr .get_user (section )
17671901 return user
17681902
1903+ def _replace_placeholder_in_section (text : str , section : str , key : str , value : str ) -> str :
1904+ """
1905+ Replace the first commented placeholder '# key = ...' in the given
1906+ [section] with an active 'key=value' line.
1907+ """
1908+ lines = text .splitlines ()
1909+ section_header = f"[{ section } ]"
1910+ inside = False
1911+
1912+ pattern = re .compile (rf"^#\s*{ re .escape (key )} \s*=\s*.*$" )
1913+
1914+ for i , line in enumerate (lines ):
1915+ stripped = line .strip ()
1916+
1917+ if stripped .startswith ("[" ) and stripped .endswith ("]" ):
1918+ inside = stripped == section_header
1919+ continue
1920+
1921+ if not inside :
1922+ continue
1923+
1924+ if pattern .match (stripped ):
1925+ lines [i ] = f"{ key } ={ value } "
1926+ break
1927+
1928+ return "\n " .join (lines ) + "\n "
1929+
17691930
1770- def write_initial_config (conffile , entries , custom_template = '' , creds_mgr_descriptor = None ):
1931+ def write_initial_config (conffile , entries , custom_template = "" , creds_mgr_descriptor = None ):
17711932 """
1772- write osc's intial configuration file. entries is a dict which contains values
1933+ write osc's initial configuration file. entries is a dict which contains values
17731934 for the config file (e.g. { 'user' : 'username', 'pass' : 'password' } ).
17741935 custom_template is an optional configuration template.
17751936 """
17761937 conf_template = custom_template or new_conf_template
1777- config = globals ()["config" ].dict ()
1778- config .update (entries )
1779- sio = StringIO (conf_template .strip () % config )
1938+
1939+ base_cfg = globals ()["config" ]
1940+ apiurl = entries .get ("apiurl" ) or base_cfg .apiurl
1941+ user = entries ["user" ]
1942+ passwd = entries ["pass" ]
1943+
1944+ text = conf_template .strip ().replace ("%(apiurl)s" , apiurl )
1945+
1946+ text = _replace_placeholder_in_section (text , apiurl , "user" , user )
1947+ if passwd :
1948+ text = _replace_placeholder_in_section (text , apiurl , "pass" , passwd )
1949+
1950+ # Will be set to actual value by credential manager; remove the comment here
1951+ text = _replace_placeholder_in_section (text , apiurl , "credentials_mgr_class" , "" )
1952+
1953+ sio = StringIO (text )
17801954 cp = OscConfigParser .OscConfigParser ()
17811955 cp .read_file (sio )
1782- cp . set ( config [ 'apiurl' ], 'user' , config [ 'user' ])
1956+
17831957 if creds_mgr_descriptor :
17841958 creds_mgr = creds_mgr_descriptor .create (cp )
17851959 else :
1786- creds_mgr = _get_credentials_manager (config ['apiurl' ], cp )
1787- creds_mgr .set_password (config ['apiurl' ], config ['user' ], config ['pass' ])
1960+ creds_mgr = _get_credentials_manager (apiurl , cp )
1961+ creds_mgr .set_password (apiurl , user , passwd )
1962+
17881963 write_config (conffile , cp )
17891964
17901965
@@ -1811,10 +1986,18 @@ def add_section(filename, url, user, passwd, creds_mgr_descriptor=None, allow_ht
18111986
18121987
18131988def _get_credentials_manager (url , cp ):
1814- if cp .has_option (url , credentials .AbstractCredentialsManager .config_entry ):
1989+ try :
1990+ entry = cp .get (url , credentials .AbstractCredentialsManager .config_entry , raw = True )
1991+ except OscConfigParser .configparser .NoOptionError :
1992+ entry = None
1993+
1994+ if entry is not None :
1995+ entry = entry .strip ()
1996+
1997+ if entry :
18151998 creds_mgr = credentials .create_credentials_manager (url , cp )
18161999 if creds_mgr is None :
1817- msg = f' Unable to instantiate creds mgr (section: { url } )'
2000+ msg = f" Unable to instantiate creds mgr (section: { url } )"
18182001 conffile = get_configParser .conffile
18192002 raise oscerr .ConfigMissingCredentialsError (msg , conffile , url )
18202003 return creds_mgr
@@ -2157,19 +2340,49 @@ def interactive_config_setup(conffile, apiurl, initial=True):
21572340
21582341 apiurl_no_scheme = urlsplit (apiurl )[1 ] or apiurl
21592342 user_prompt = f"Username [{ apiurl_no_scheme } ]: "
2160- user = raw_input (user_prompt )
2343+ user = ""
2344+ max_attempts = 3
2345+ for attempt in range (max_attempts ):
2346+ user = raw_input (user_prompt ).strip ()
2347+ if user :
2348+ break
2349+ remaining = max_attempts - attempt - 1
2350+ if remaining > 0 :
2351+ print ("Username must not be empty, please try again." , file = sys .stderr )
2352+ else :
2353+ print ("No username entered. Aborting." , file = sys .stderr )
2354+ raise oscerr .UserAbort ()
2355+
21612356 pass_prompt = f"Password [{ user } @{ apiurl_no_scheme } ]: "
21622357 passwd = getpass .getpass (pass_prompt )
2163- creds_mgr_descr = select_credentials_manager_descr ()
2358+ if not passwd :
2359+ print ("Password is empty. To use SSH, enter no password again." )
2360+ pass_prompt = f"Password [{ user } @{ apiurl_no_scheme } ]: "
2361+ passwd = getpass .getpass (pass_prompt )
2362+ if not passwd :
2363+ print ("Password is empty. Use SSH or run 'osc initconfig' to re-create the config file." )
2364+
21642365 if initial :
2165- config = {' user' : user , ' pass' : passwd }
2366+ config = {" user" : user , " pass" : passwd }
21662367 if apiurl :
2167- config [' apiurl' ] = apiurl
2368+ config [" apiurl" ] = apiurl
21682369 if http :
2169- config ['allow_http' ] = 1
2170- write_initial_config (conffile , config , creds_mgr_descriptor = creds_mgr_descr )
2370+ config ["allow_http" ] = 1
2371+
2372+ write_initial_config (
2373+ conffile ,
2374+ config ,
2375+ creds_mgr_descriptor = select_credentials_manager_descr (),
2376+ )
21712377 else :
2172- add_section (conffile , apiurl , user , passwd , creds_mgr_descriptor = creds_mgr_descr , allow_http = http )
2378+ add_section (
2379+ conffile ,
2380+ apiurl ,
2381+ user ,
2382+ passwd ,
2383+ creds_mgr_descriptor = select_credentials_manager_descr (),
2384+ allow_http = http ,
2385+ )
21732386
21742387
21752388def select_credentials_manager_descr ():
0 commit comments