yandrey321 commented on code in PR #9247:
URL: https://github.com/apache/ozone/pull/9247#discussion_r2651244999


##########
tools/installer/ozone_installer.py:
##########
@@ -0,0 +1,499 @@
+#!/usr/bin/env python3
+
+import argparse
+import json
+import os
+import re
+import shlex
+import subprocess
+import sys
+import tempfile
+from pathlib import Path
+from typing import List, Optional, Tuple
+
+ANSIBLE_ROOT = Path(__file__).resolve().parent
+ANSIBLE_CFG = ANSIBLE_ROOT / "ansible.cfg"
+PLAYBOOKS_DIR = ANSIBLE_ROOT / "playbooks"
+LOGS_DIR = ANSIBLE_ROOT / "logs"
+LAST_FAILED_FILE = LOGS_DIR / "last_failed_task.txt"
+LAST_RUN_FILE = LOGS_DIR / "last_run.json"
+
+DEFAULTS = {
+    "install_base": "/opt/ozone",
+    "data_base": "/data/ozone",
+    "ozone_version": "2.0.0",
+    "jdk_major": 17,
+    "service_user": "ozone",
+    "service_group": "ozone",
+    "dl_url": "https://dlcdn.apache.org/ozone";,
+    "JAVA_MARKER": "Apache Ozone Installer Java Home",
+    "ENV_MARKER": "Apache Ozone Installer Env",
+    "start_after_install": True,
+    "use_sudo": True,
+}
+
+def parse_args(argv):
+    p = argparse.ArgumentParser(
+        description="Ozone Ansible Installer (Python trigger) - mirrors bash 
installer flags"
+    )
+    p.add_argument("-H", "--host", help="Target host(s). Non-HA: host. HA: 
comma-separated or brace expansion host{1..n}")
+    p.add_argument("-m", "--auth-method", choices=["password", "key"], 
default=None)
+    p.add_argument("-p", "--password", help="SSH password (for 
--auth-method=password)")
+    p.add_argument("-k", "--keyfile", help="SSH private key file (for 
--auth-method=key)")
+    p.add_argument("-v", "--version", help="Ozone version (e.g., 2.0.0) or 
'local'")
+    p.add_argument("-i", "--install-dir", help=f"Install root (default: 
{DEFAULTS['install_base']})")
+    p.add_argument("-d", "--data-dir", help=f"Data root (default: 
{DEFAULTS['data_base']})")
+    p.add_argument("-s", "--start", action="store_true", help="Initialize and 
start after install")
+    p.add_argument("-M", "--cluster-mode", choices=["non-ha", "ha"], 
help="Force cluster mode (default: auto by host count)")
+    p.add_argument("-r", "--role-file", help="Role file (YAML) for HA mapping 
(optional)")
+    p.add_argument("-j", "--jdk-version", type=int, choices=[17, 21], 
help="JDK major version (default: 17)")
+    p.add_argument("-c", "--config-dir", help="Config dir (optional, templates 
are used by default)")
+    p.add_argument("-x", "--clean", action="store_true", help="(Reserved) 
Cleanup before install [not yet implemented]")
+    p.add_argument("-l", "--ssh-user", help="SSH username (default: root)")
+    p.add_argument("-S", "--use-sudo", action="store_true", help="Run remote 
commands via sudo (default)")
+    p.add_argument("-u", "--service-user", help="Service user (default: 
ozone)")
+    p.add_argument("-g", "--service-group", help="Service group (default: 
ozone)")
+    # Local extras
+    p.add_argument("--local-path", help="Path to local Ozone build (contains 
bin/ozone)")
+    p.add_argument("--dl-url", help="Upstream download base URL")
+    p.add_argument("--yes", action="store_true", help="Non-interactive; accept 
defaults for missing values")
+    p.add_argument("-R", "--resume", action="store_true", help="Resume play at 
last failed task (if available)")
+    return p.parse_args(argv)
+
+def _validate_local_ozone_dir(path: Path) -> bool:
+    """
+    Returns True if 'path/bin/ozone' exists and is executable.
+    """
+    ozone_bin = path / "bin" / "ozone"
+    try:
+        return ozone_bin.exists() and os.access(str(ozone_bin), os.X_OK)
+    except OSError:
+        return False
+
+def prompt(prompt_text, default=None, secret=False, yes_mode=False):
+    if yes_mode:
+        return default
+    try:
+        if default:
+            text = f"{prompt_text} [{default}]: "
+        else:
+            text = f"{prompt_text}: "
+        if secret:
+            import getpass
+            val = getpass.getpass(text)
+        else:
+            val = input(text)
+        if not val and default is not None:
+            return default
+        return val
+    except EOFError:
+        return default
+
+def _semver_key(v: str) -> Tuple[int, int, int, str]:
+    """
+    Convert version like '2.0.0' or '2.1.0-RC0' to a sortable key.
+    Pre-release suffix sorts before final.
+    """
+    try:
+        core, *rest = v.split("-", 1)
+        major, minor, patch = core.split(".")
+        suffix = rest[0] if rest else ""
+        return (int(major), int(minor), int(patch), suffix)
+    except Exception:
+        return (0, 0, 0, v)
+
+def fetch_available_versions(dl_url: str, limit: int = 30) -> List[str]:
+    """
+    Fetch available Ozone versions from the download base. Returns 
newest-first.
+    """
+    try:
+        import urllib.request
+        with urllib.request.urlopen(dl_url, timeout=10) as resp:
+            html = resp.read().decode("utf-8", errors="ignore")
+        # Apache directory listing usually has anchors like href="2.0.0/"
+        candidates = set(m.group(1) for m in 
re.finditer(r'href="([0-9]+\.[0-9]+\.[0-9]+(?:-[A-Za-z0-9]+)?)\/"', html))
+        versions = sorted(candidates, key=_semver_key, reverse=True)
+        if limit and len(versions) > limit:
+            versions = versions[:limit]
+        return versions
+    except Exception:
+        return []
+
+def choose_version_interactive(versions: List[str], default_version: str, 
yes_mode: bool) -> Optional[str]:
+    """
+    Present a numbered list and prompt user to choose a version.
+    Returns selected version string or None if not chosen.
+    """
+    if not versions:
+        return None
+    if yes_mode:
+        return versions[0]
+    print("Available Ozone versions:")
+    for idx, ver in enumerate(versions, start=1):
+        print(f"  {idx}) {ver}")
+    while True:
+        choice = prompt("Select version by number or type a version string (or 
'local')", default="1", yes_mode=False)
+        if choice is None or str(choice).strip() == "":
+            return versions[0]
+        choice = str(choice).strip()
+        if choice.lower() == "local":
+            return "local"
+        if choice.isdigit():
+            i = int(choice)
+            if 1 <= i <= len(versions):
+                return versions[i - 1]
+        # allow typing a specific version not listed
+        if re.match(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-[A-Za-z0-9]+)?$", choice):
+            return choice
+        print("Invalid selection. Please enter a number from the list, a valid 
version (e.g., 2.0.0) or 'local'.")
+
+def expand_braces(expr):
+    # Supports simple pattern like prefix{1..N}suffix
+    if not expr or "{" not in expr or ".." not in expr or "}" not in expr:
+        return [expr]
+    m = re.search(r"(.*)\{(\d+)\.\.(\d+)\}(.*)", expr)
+    if not m:
+        return [expr]
+    pre, a, b, post = m.group(1), int(m.group(2)), int(m.group(3)), m.group(4)
+    return [f"{pre}{i}{post}" for i in range(a, b + 1)]
+
+def parse_hosts(hosts_raw):
+    """
+    Accepts comma-separated hosts; each may contain brace expansion.
+    Returns list of dicts: {host, user, port}
+    """
+    if not hosts_raw:
+        return []
+    out = []
+    for token in hosts_raw.split(","):
+        token = token.strip()
+        expanded = expand_braces(token)
+        for item in expanded:
+            user = None
+            hostport = item
+            if "@" in item:
+                user, hostport = item.split("@", 1)
+            host = hostport
+            port = None
+            if ":" in hostport:
+                host, port = hostport.split(":", 1)
+            out.append({"host": host, "user": user, "port": port})
+    return out
+
+def auto_cluster_mode(hosts, forced=None):

Review Comment:
   missing return type



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to