GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — devstack/scripts/lpb-devstack at dev · lpb-stack/devstack Expand file tree/ Copy pathlpb-devstackMore file actions executable file ·461 lines (400 loc) · 20.2 KB / Copy pathlpb-devstackFile metadata and controlsexecutable file ·461 lines (400 loc) · 20.2 KB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 #!/usr/bin/env python3 """lpb-devstack — LocalPibox DevOps workspace tool. For developers working on the stack (workspace + GitHub). Config repo management inside the container lives in lpb-config. Usage: lpb-devstack [--tag dev|main] [--devstack-dir PATH] <command> --devstack-dir PATH devstack checkout to work on. The checkout is found by content, so its directory name is free (a worktree named anything works); when the workspace holds more than one, the command asks — or pin it here. lpb-devstack bump [--minor|--major] [--set V] [--no-commit] [--push] Bump devstack/VERSION (default: patch, suffix preserved) and commit. lpb-devstack tag-repos [--branch dev|main] [--version V] [--dry-run] Tag the 5 stack repos (excl. devstack) at the given version. lpb-devstack workspace status Show workspace repo branches + alignment. lpb-devstack workspace sync Clone/symlink/align/pull workspace repos (the single write path). The devstack repo is located by content, not by directory name. lpb-devstack validate Full stack alignment check (repos, branches, pins, env). lpb-devstack release status Stable-release readiness across all 6 repos + docs flag (non-destructive). lpb-devstack release docs-ready [--yes] Flag the docs branch as reviewed for the next stable release (merge dev→docs, build site, confirm, commit DOCS_READY + push). lpb-devstack release promote [--yes] [--dry-run] [--rebase] [--force] Promote dev branches to stable branches + push (blocked until docs are flagged ready; --force overrides). lpb-devstack validate-hooks Run the full devstack pre-commit checks (tests included). Pipeline detection: Read from the VERSION file (-lpb-dev → dev, else main). Override with --tag. Environment: LPB_WORKSPACE_ROOT — Workspace root (default: /home/lpb/workspace) LPB_DEVSTACK_DIR — devstack checkout (same as --devstack-dir) AGENT_DIR — Config repo path (default: /home/lpb/.pi/agent) LPB_AGENT_GIT — Extension clone dir (default: $AGENT_DIR/git/github.com/lpb-stack) LPB_STACK_REMOTE_BASE — Stack remote base (default: https://github.com/lpb-stack) LPB_GITHUB_TOKEN — GitHub token for authenticated git operations """ from __future__ import annotations import argparse import sys from pathlib import Path _SELF_DIR = Path(__file__).resolve().parent # Shared localpibox package location — repo layout (support/ → <devstack>/scripts/), # Docker image (/opt/pi-support), or host install (~/.lpb-stack/devstack/). for _c in ( _SELF_DIR.parent / "scripts", _SELF_DIR, Path("/opt/pi-support"), Path.home() / ".lpb-stack" / "devstack", ): if (_c / "localpibox").is_dir(): sys.path.insert(0, str(_c)) break from localpibox.cli import install_sigpipe_handler # noqa: E402 from localpibox.log import Console # noqa: E402 from localpibox.run import run_cmd # noqa: E402 from localpibox.stack.gitutil import git, git_auth # noqa: E402 from localpibox.stack.repos import TAG_REPOS, set_devstack_dir # noqa: E402 from localpibox.stack.version import ( # noqa: E402 VERSION_RE, _devstack_root_candidates, _find_version_file, bump_version, detect_pipeline, get_version, ) from localpibox.stack.workspace import ( # noqa: E402 _resolve_repo_path, cmd_workspace_status, cmd_workspace_sync, sync_pins_quiet, ) from localpibox.stack.validate import cmd_validate # noqa: E402 from localpibox.stack.release import ( # noqa: E402 cmd_release_docs_ready, cmd_release_promote, cmd_release_status, dev_release_gate, ) # ─── VERSION bumping ─────────────────────────────────────────────────────── def cmd_bump(cons: Console, *, minor: bool = False, major: bool = False, set_version: str | None = None, no_commit: bool = False, push: bool = False, force: bool = False) -> int: """Bump devstack/VERSION and commit it (push is manual — CI builds on it).""" vf = _find_version_file() if vf is None: cons.error("VERSION file not found (looked in the devstack root, " "/opt/devstack, and $LPB_WORKSPACE_ROOT/devstack)") return 1 current = vf.read_text().strip() if set_version is not None: if not VERSION_RE.match(set_version): cons.error(f"invalid version: {set_version!r} (expected 0.x.y-lpb[-dev])") return 1 new = set_version else: kind = "major" if major else ("minor" if minor else "patch") try: new = bump_version(current, kind) except ValueError as e: cons.error(str(e)) cons.info(f"Use --set 0.x.y-lpb[-dev] to set an explicit version.") return 1 if new == current: cons.warn(f"VERSION unchanged ({current})") return 0 # Dev release gate: CI tags the REMOTE dev heads of the other repos, so # unpushed/uncommitted local work would ship invisibly. Block unless --force. if not force: problems = dev_release_gate(cons) if problems: cons.error(f"Dev release gate: {problems} problem(s) — fix and re-run, " f"or bypass with --force") return 1 else: cons.warn("Dev release gate skipped (--force)") vf.write_text(new + "\n") cons.info(f"VERSION: {current} → {new}") # Keep settings.json pins in lockstep with the new VERSION, BEFORE the # commit: the pre-commit validate checks pins == VERSION, and syncing # after the hook would target the OLD version (the hook runs before the # bump lands). Without this, the first dev bump after a stable release # (pins still at the stable tag) fails its own gate. from localpibox.stack.repos import DEFAULT_AGENT_DIR as _agent_dir n = sync_pins_quiet(_agent_dir, new, cons) if n: cons.info(f"Synced {n} extension pin(s) to {new} " f"(run 'pi update --extensions' after the tags exist)") if no_commit: cons.info("--no-commit: file written, not committed (review + commit manually)") return 0 repo_root = vf.parent out, _, code = git(repo_root, "rev-parse", "--is-inside-work-tree") if code != 0 or out.strip() != "true": cons.error(f"{repo_root} is not a git worktree — commit the VERSION change manually") return 1 git(repo_root, "add", "VERSION") cons.info("Committing (pre-commit hook runs the test suite — may take a minute)...") out, err, code = git(repo_root, "commit", "-m", f"chore: bump VERSION {new}", timeout=900) if code != 0: cons.error(f"Commit failed: {(err.strip() or out.strip())[:200]}") return 1 cons.info(f"Committed: chore: bump VERSION {new}") if push: branch_out, _, bcode = git(repo_root, "branch", "--show-current") branch = branch_out.strip() if bcode != 0 or not branch: cons.error("Not on a branch — push manually (git push origin <branch>)") return 1 out, err, code = git_auth(repo_root, "push", "origin", branch, timeout=180) if code != 0: cons.error(f"Push failed: {(err.strip() or out.strip())[:200]}") return 1 cons.info(f"Pushed {branch} — CI will build + tag {new}") else: cons.info("Push to trigger the CI build + tag: git push origin <branch>") cons.warn("The bump must be the TIP of the push: CI's VERSION_CHECK diffs " "the pushed tip commit for VERSION changes. Commits made after " "this one make the release a no-op (tests-only run) — if you " "add follow-up commits, run 'lpb-devstack bump' again so the " "bump is pushed last.") return 0 # ─── Repo tagging ────────────────────────────────────────────────────────── def _tag_repo_hint(name: str) -> str: return ("$AGENT_DIR" if name == "config" else f"$LPB_WORKSPACE_ROOT/{name}") def cmd_tag_repos(cons: Console, *, pipeline: str = "dev", version: str | None = None, dry_run: bool = False) -> int: """Tag the 5 stack repos (excl. devstack) at *version* on the pipeline's branches. Uses the local workspace clones (fetch branch → push tag ref), so git auth (LPB_GITHUB_TOKEN / credential helper) is used and no objects need to be transferred. """ if version is None: version = get_version() if not VERSION_RE.match(version): cons.error(f"invalid version: {version!r} (expected 0.x.y-lpb[-dev])") return 1 # Best-effort: warn when the local devstack VERSION is not what's committed. root = _find_version_file() if root is not None: out, _, code = git(root.parent, "show", "HEAD:VERSION") if code == 0 and out.strip() and out.strip() != version: cons.warn(f"committed devstack VERSION is {out.strip()} (tagging {version}) — " "run 'lpb-devstack bump' first for a normal release") cons.info(f"Tagging 5 repos at {version} (pipeline: {pipeline})") if dry_run: cons.info("(dry run — nothing will be pushed)") cons.info("") failures = 0 for name, dev_b, main_b in TAG_REPOS: # Branch comes from the TAG_REPOS entry (mirrors the CI tag-repos # job); do not re-derive via expected_branch() — it would ignore # the actual tagging set and fail for repos no longer in the stack. b = dev_b if pipeline == "dev" else main_b path = _resolve_repo_path(name, cons) if path is None: cons.error(f" {name}: no local clone at {_tag_repo_hint(name)} — " f"run 'lpb-devstack workspace sync' / 'lpb-config update' first") return 1 out, err, code = git_auth(path, "fetch", "--prune", "origin", "--quiet", timeout=180) if code != 0: cons.error(f" {name}: fetch failed ({(err.strip() or out.strip())[:80]})") return 1 sha_out, _, sha_code = git(path, "rev-parse", f"origin/{b}") sha = sha_out.strip() if sha_code != 0 or not sha: cons.error(f" {name}: branch '{b}' not found on origin — aborting (fail fast)") return 1 existing, _, _ = git_auth(path, "ls-remote", "origin", f"refs/tags/{version}", timeout=60) if existing.strip(): cons.info(f" {name}: {version} already exists — skipped") continue if dry_run: cons.info(f" {name}: would tag {b}@{sha[:8]} → {version}") continue out, err, code = git_auth(path, "push", "origin", f"{sha}:refs/tags/{version}", timeout=120) if code == 0: cons.info(f" ✅ {name}@{version} ({b}@{sha[:8]})") else: cons.error(f" ❌ {name}: tag push failed ({(err.strip() or out.strip())[:120]})") failures += 1 if dry_run: cons.info("") cons.info("Dry run — nothing was changed.") return 0 if failures: cons.error(f"{failures} repo(s) failed — re-run to complete tagging " "(already-tagged repos are skipped)") return 1 cons.done(f"All 5 repos tagged at {version}") return 0 # ─── Pre-commit validation ───────────────────────────────────────────────── def _find_devstack_root() -> Path | None: """Locate the devstack repo root (cwd first, then known locations).""" cwd = Path.cwd() for p in [cwd, *cwd.parents]: if (p / ".githooks" / "pre-commit").is_file() and (p / "VERSION").is_file(): return p for c in _devstack_root_candidates(): if (c / ".githooks" / "pre-commit").is_file(): return c return None def cmd_validate_hooks(cons: Console) -> int: """Run the full devstack pre-commit checks (VERSION, pins, cleanliness, tests).""" root = _find_devstack_root() if root is None: cons.error("devstack repo with .githooks/pre-commit not found") cons.info("Run this from inside the devstack workspace, or set LPB_WORKSPACE_ROOT.") return 1 hook = root / ".githooks" / "pre-commit" cons.info(f"Running pre-commit checks: {hook}") cons.info("(includes the full test suite — may take a minute)") out, err, code = run_cmd(["bash", str(hook)], cwd=str(root), timeout=1800) if out.strip(): cons.raw(out.rstrip()) if err.strip(): cons.raw(err.rstrip()) if code != 0: cons.error("pre-commit checks FAILED (exit %d)" % code) return 1 cons.done("pre-commit checks passed") return 0 # ─── CLI ────────────────────────────────────────────────────────────────── def _add_subparser(sub, name: str, help_: str) -> argparse.ArgumentParser: p = sub.add_parser(name, help=help_) return p def main(argv: list[str] | None = None) -> int: install_sigpipe_handler() parser = argparse.ArgumentParser( prog="lpb-devstack", description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, epilog=( "Environment:\n" " LPB_WORKSPACE_ROOT Workspace root (default: /home/lpb/workspace)\n" " LPB_DEVSTACK_DIR devstack checkout (same as --devstack-dir)\n" " AGENT_DIR Config repo path (default: /home/lpb/.pi/agent)\n" " LPB_AGENT_GIT Extension clone dir (default: $AGENT_DIR/git/github.com/lpb-stack)\n" " LPB_STACK_REMOTE_BASE Stack remote base (default: https://github.com/lpb-stack)\n" " LPB_GITHUB_TOKEN GitHub token for authenticated git operations\n\n" "Pipeline:\n" " Detected from VERSION file or LPB_IMAGE_TAG env var.\n" " Override with --tag dev|main on any command." ), ) parser.add_argument( "--tag", default=None, help="Override pipeline detection (dev|main)", ) parser.add_argument( "--devstack-dir", dest="devstack_dir", metavar="PATH", default=None, help="devstack checkout to operate on (default: detected under " "$LPB_WORKSPACE_ROOT by content, any directory name)", ) sub = parser.add_subparsers(dest="command") # VERSION bumping p_bump = _add_subparser(sub, "bump", "Bump devstack/VERSION and commit") p_bump.add_argument("--minor", action="store_true", help="bump minor (0.0.9 → 0.1.0)") p_bump.add_argument("--major", action="store_true", help="bump major (0.9.9 → 1.0.0)") p_bump.add_argument("--set", dest="set_version", metavar="V", help="explicit version (0.x.y-lpb[-dev])") p_bump.add_argument("--no-commit", action="store_true", help="write VERSION but don't commit") p_bump.add_argument("--push", action="store_true", help="commit + push (triggers the CI build + tag)") p_bump.add_argument("--force", action="store_true", help="skip the dev release gate (other repos pushed/committed check)") # Repo tagging p_tag = _add_subparser(sub, "tag-repos", "Tag the 5 stack repos at the committed VERSION") p_tag.add_argument("--branch", choices=["dev", "main"], help="tag on dev branches (dev) or stable branches (main)") p_tag.add_argument("--version", dest="version", metavar="V", help="explicit version (default: devstack VERSION file)") p_tag.add_argument("--dry-run", action="store_true", help="show the plan, change nothing") # Workspace management p_ws = sub.add_parser("workspace", help="Manage workspace repos") ws_sub = p_ws.add_subparsers(dest="workspace_command") _add_subparser(ws_sub, "status", "Show workspace repo branches + alignment") _add_subparser(ws_sub, "sync", "Prepare workspace: clone missing repos, create symlinks, align branches, pull latest") # Stack validation _add_subparser(sub, "validate", "Full stack alignment check (repos, branches, pins, env)") # Release promotion p_rel = sub.add_parser("release", help="Stable release: promote dev branches to stable") rel_sub = p_rel.add_subparsers(dest="release_command") _add_subparser(rel_sub, "status", "Show stable-release readiness across all repos + docs flag") p_rel_docs = _add_subparser(rel_sub, "docs-ready", "Flag the docs branch as reviewed for the next stable release") p_rel_docs.add_argument("--yes", action="store_true", help="skip the review confirmation") p_rel_promote = _add_subparser(rel_sub, "promote", "Merge dev branches into stable branches + push") p_rel_promote.add_argument("--yes", action="store_true", help="skip confirmation") p_rel_promote.add_argument("--dry-run", action="store_true", help="show plan + feasibility, change nothing") p_rel_promote.add_argument("--rebase", action="store_true", help="first-release mode: for repos with unrelated " "histories, replace stable with dev history " "(force-push)") p_rel_promote.add_argument("--force", action="store_true", help="promote even if docs are not flagged ready") # Pre-commit validation _add_subparser(sub, "validate-hooks", "Run the full devstack pre-commit checks (tests included)") args = parser.parse_args(argv) cons = Console() # Pin the devstack checkout before anything reads VERSION/stack env. set_devstack_dir(args.devstack_dir) if args.tag is not None and args.tag not in ("dev", "main"): cons.error(f"invalid --tag: {args.tag} (expected dev|main)") return 1 pipeline = detect_pipeline(args.tag) if args.command == "bump": return cmd_bump(cons, minor=getattr(args, "minor", False), major=getattr(args, "major", False), set_version=getattr(args, "set_version", None), no_commit=getattr(args, "no_commit", False), push=getattr(args, "push", False), force=getattr(args, "force", False)) if args.command == "tag-repos": return cmd_tag_repos(cons, pipeline=pipeline, version=getattr(args, "version", None), dry_run=getattr(args, "dry_run", False)) if args.command == "workspace": if not args.workspace_command: p_ws.print_help() return 1 if args.workspace_command == "status": return cmd_workspace_status(pipeline, cons) if args.workspace_command == "sync": return cmd_workspace_sync(pipeline, cons) if args.command == "validate": return cmd_validate(pipeline, cons) if args.command == "release": if not args.release_command: p_rel.print_help() return 1 if args.release_command == "status": return cmd_release_status(cons) if args.release_command == "docs-ready": return cmd_release_docs_ready(assume_yes=getattr(args, "yes", False), cons=cons) if args.release_command == "promote": return cmd_release_promote(assume_yes=getattr(args, "yes", False), dry_run=getattr(args, "dry_run", False), rebase=getattr(args, "rebase", False), force=getattr(args, "force", False), cons=cons) if args.command == "validate-hooks": return cmd_validate_hooks(cons) parser.print_help() return 1 if __name__ == "__main__": raise SystemExit(main()) |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |