aap-demo 2.0.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. aap_demo/__init__.py +6 -0
  2. aap_demo/__main__.py +10 -0
  3. aap_demo/cli/__init__.py +0 -0
  4. aap_demo/cli/_schema_args.py +137 -0
  5. aap_demo/cli/addons.py +21 -0
  6. aap_demo/cli/completion.py +16 -0
  7. aap_demo/cli/day2.py +17 -0
  8. aap_demo/cli/gui.py +12 -0
  9. aap_demo/cli/lifecycle.py +156 -0
  10. aap_demo/cli/main.py +565 -0
  11. aap_demo/cli/observe.py +68 -0
  12. aap_demo/cli/ops.py +378 -0
  13. aap_demo/cluster/__init__.py +3 -0
  14. aap_demo/cluster/aap.py +907 -0
  15. aap_demo/cluster/bootstrap.py +662 -0
  16. aap_demo/cluster/catalog_signature.py +575 -0
  17. aap_demo/cluster/coredns.py +337 -0
  18. aap_demo/cluster/deploy.py +369 -0
  19. aap_demo/cluster/ingress_ca.py +316 -0
  20. aap_demo/cluster/kubeconfig.py +190 -0
  21. aap_demo/cluster/namespace.py +136 -0
  22. aap_demo/cluster/olm.py +276 -0
  23. aap_demo/cluster/preflight.py +134 -0
  24. aap_demo/cluster/pull_secret.py +380 -0
  25. aap_demo/cluster/runtime.py +324 -0
  26. aap_demo/cluster/scc.py +174 -0
  27. aap_demo/cluster/status.py +582 -0
  28. aap_demo/cluster/storage.py +73 -0
  29. aap_demo/core/__init__.py +0 -0
  30. aap_demo/core/bash_v1.py +171 -0
  31. aap_demo/core/config.py +219 -0
  32. aap_demo/core/console.py +907 -0
  33. aap_demo/core/context.py +65 -0
  34. aap_demo/core/errors.py +84 -0
  35. aap_demo/core/events.py +135 -0
  36. aap_demo/core/migration.py +369 -0
  37. aap_demo/core/output.py +43 -0
  38. aap_demo/core/paths.py +173 -0
  39. aap_demo/core/prompts.py +51 -0
  40. aap_demo/core/schema.py +164 -0
  41. aap_demo/core/secrets.py +221 -0
  42. aap_demo/core/version.py +101 -0
  43. aap_demo/core/waiting.py +89 -0
  44. aap_demo/data/__init__.py +27 -0
  45. aap_demo/data/crc-release +1 -0
  46. aap_demo/data/crs/__init__.py +1 -0
  47. aap_demo/data/crs/aap-controller-eda.yaml +16 -0
  48. aap_demo/data/crs/aap-controller.yaml +16 -0
  49. aap_demo/data/crs/aap-minimal-noingress.yaml +63 -0
  50. aap_demo/data/crs/aap-minimal-rwx.yaml +21 -0
  51. aap_demo/data/crs/aap-minimal.yaml +48 -0
  52. aap_demo/data/manifests/__init__.py +1 -0
  53. aap_demo/data/manifests/aap-storage-pvcs.yaml +31 -0
  54. aap_demo/data/manifests/nfs-provisioner.yaml +113 -0
  55. aap_demo/data/manifests/nfs-server.yaml +76 -0
  56. aap_demo/data/olm/__init__.py +1 -0
  57. aap_demo/data/olm/catalogsource.yaml +27 -0
  58. aap_demo/data/olm/operatorgroup.yaml +15 -0
  59. aap_demo/data/olm/subscription.yaml +18 -0
  60. aap_demo/data/schema/__init__.py +0 -0
  61. aap_demo/data/schema/addons.yaml +30 -0
  62. aap_demo/data/schema/core.yaml +59 -0
  63. aap_demo/data/schema/crc.yaml +78 -0
  64. aap_demo/data/schema/deploy.yaml +74 -0
  65. aap_demo/data/schema/gui.yaml +52 -0
  66. aap_demo/data/schema/playbooks.yaml +88 -0
  67. aap_demo/data/schema/secrets.yaml +19 -0
  68. aap_demo/data/schema/trust.yaml +28 -0
  69. aap_demo/diagnostics/__init__.py +3 -0
  70. aap_demo/diagnostics/checks.py +388 -0
  71. aap_demo/diagnostics/report.py +55 -0
  72. aap_demo/exec/__init__.py +0 -0
  73. aap_demo/exec/kubectl.py +65 -0
  74. aap_demo/exec/oc.py +58 -0
  75. aap_demo/exec/operator_sdk.py +140 -0
  76. aap_demo/exec/runner.py +366 -0
  77. aap_demo/exec/ssh.py +152 -0
  78. aap_demo/infra/__init__.py +9 -0
  79. aap_demo/infra/crc.py +445 -0
  80. aap_demo/infra/crc_install.py +280 -0
  81. aap_demo-2.0.0.dev0.dist-info/METADATA +291 -0
  82. aap_demo-2.0.0.dev0.dist-info/RECORD +85 -0
  83. aap_demo-2.0.0.dev0.dist-info/WHEEL +4 -0
  84. aap_demo-2.0.0.dev0.dist-info/entry_points.txt +2 -0
  85. aap_demo-2.0.0.dev0.dist-info/licenses/LICENSE +202 -0
aap_demo/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """aap-demo — deploy Ansible Automation Platform 2.7 to OpenShift Local."""
2
+
3
+ # Development release published from the v2 branch. pip ignores ``.dev`` versions.
4
+ __version__ = "2.0.0.dev0"
5
+
6
+ __all__ = ["__version__"]
aap_demo/__main__.py ADDED
@@ -0,0 +1,10 @@
1
+ """``python -m aap_demo`` — the same code path as the ``aap-demo`` console script."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ from aap_demo.cli.main import main
8
+
9
+ if __name__ == "__main__":
10
+ sys.exit(main())
File without changes
@@ -0,0 +1,137 @@
1
+ """Generate argparse arguments from a JSON Schema document (design §9.3).
2
+
3
+ Three deliberate omissions, each with a reason:
4
+
5
+ * ``minimum``/``maximum`` are not turned into argparse type-checkers. The merged
6
+ result is validated by ``jsonschema`` immediately afterwards, and a second
7
+ range check in a second place is exactly the drift this mechanism exists to
8
+ prevent — argparse handles *parsing*, jsonschema handles *validity*.
9
+ * ``required`` is not enforced here: a value may arrive from config or an env
10
+ var, so requiredness is a property of the resolved document.
11
+ * Nested objects are not flattened into dotted flags. A nested section gets its
12
+ own document and its own ``add_schema_arguments`` call against the same
13
+ parser, which is how ``crc.*`` and ``deploy.*`` both land on ``aap-demo create``.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ from typing import Any, Dict, Mapping, Optional
20
+
21
+ from aap_demo.core import schema
22
+
23
+ _TYPES = {"string": str, "integer": int, "number": float}
24
+
25
+ #: Options that may be hand-written because they are verbs or output modifiers,
26
+ #: not settings — they have no config or GUI analogue. ``test_schema_parity``
27
+ #: fails on any optional argument outside this list that is not schema-derived.
28
+ #: The design's §3.1 allowlist is the first group; the rest are per-command
29
+ #: modifiers of the same kind (a flag that changes what a command *does*, not a
30
+ #: value it persists).
31
+ CLI_ONLY_OPTIONS = frozenset(
32
+ {
33
+ # §3.1's explicit allowlist
34
+ "--output",
35
+ "--quiet",
36
+ "-q",
37
+ "--yes",
38
+ "-y",
39
+ "--config",
40
+ "--verbose",
41
+ "-v",
42
+ "-h",
43
+ "--help",
44
+ # global, CLI-shaped: they select a target or an escape hatch, not a setting
45
+ "--kubeconfig",
46
+ "--context",
47
+ "--force",
48
+ "--set",
49
+ "--version",
50
+ "-V",
51
+ # per-command verb modifiers
52
+ "--reset",
53
+ "--ai",
54
+ "--all",
55
+ "--dry-run",
56
+ "--check",
57
+ "--purge-creds",
58
+ "--refresh-catalog",
59
+ "--extra-vars",
60
+ "-e",
61
+ "--stdin",
62
+ }
63
+ )
64
+
65
+
66
+ def _dest_for(section_name: str, key: str) -> str:
67
+ return f"{section_name}__{key}"
68
+
69
+
70
+ def add_schema_arguments(
71
+ parser: argparse.ArgumentParser,
72
+ section_name: str,
73
+ *,
74
+ resolved: Optional[Mapping[str, Any]] = None,
75
+ doc: Optional[Dict[str, Any]] = None,
76
+ suppress: bool = False,
77
+ ) -> None:
78
+ """Add one argparse argument per property in a schema section.
79
+
80
+ ``resolved`` is the post-config-resolution value map, so ``--help`` shows the
81
+ *effective* default rather than the schema default. ``suppress`` builds the
82
+ same parser with ``SUPPRESS`` defaults, which is how ``cli/main.py`` learns
83
+ which values the user actually typed and which merely defaulted.
84
+ """
85
+ document = doc if doc is not None else schema.section(section_name)
86
+ values = resolved or {}
87
+
88
+ properties = document.get("properties", {})
89
+ for key, prop in sorted(properties.items(), key=lambda kv: kv[1].get("x-order", 1000)):
90
+ flags = schema.cli_flag(prop, key)
91
+ if flags is None:
92
+ continue
93
+
94
+ default = values.get(key, prop.get("default"))
95
+ kwargs: Dict[str, Any] = {
96
+ "dest": _dest_for(section_name, key),
97
+ "help": prop.get("description"),
98
+ "default": argparse.SUPPRESS if suppress else default,
99
+ }
100
+
101
+ prop_type = prop.get("type")
102
+ if prop_type == "boolean":
103
+ kwargs["action"] = "store_true" if not default else "store_false"
104
+ elif prop_type in _TYPES:
105
+ kwargs["type"] = _TYPES[prop_type]
106
+ if "enum" in prop:
107
+ # No metavar for an enum: argparse then renders the choices
108
+ # themselves in --help, which is the useful thing to show. A
109
+ # metavar would replace them with the widget name.
110
+ kwargs["choices"] = prop["enum"]
111
+ else:
112
+ kwargs["metavar"] = prop.get("x-gui", {}).get("widget", key).upper()
113
+ else:
114
+ raise ValueError(
115
+ f"{section_name}.{key}: type {prop_type!r} has no argparse mapping; "
116
+ "mark it 'x-cli: false' or give it a scalar type"
117
+ )
118
+
119
+ parser.add_argument(*flags, **kwargs)
120
+
121
+
122
+ def collect_overrides(namespace: argparse.Namespace) -> Dict[str, Dict[str, Any]]:
123
+ """Pull schema-derived values back out of a parsed namespace.
124
+
125
+ Only values the user actually supplied should override config, so callers
126
+ pass the *supplied* namespace produced by a second parse with suppressed
127
+ defaults; see ``cli/main.py``.
128
+ """
129
+ overrides: Dict[str, Dict[str, Any]] = {}
130
+ for dest, value in vars(namespace).items():
131
+ if "__" not in dest:
132
+ continue
133
+ section_name, _, key = dest.partition("__")
134
+ if section_name not in schema.sections():
135
+ continue
136
+ overrides.setdefault(section_name, {})[key] = value
137
+ return overrides
aap_demo/cli/addons.py ADDED
@@ -0,0 +1,21 @@
1
+ """Addon commands — phase 0 stubs (the addon platform lands in phase 5+6)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from aap_demo.core.context import AppContext
8
+ from aap_demo.core.errors import NotImplementedYetError
9
+
10
+
11
+ def enable(ctx: AppContext, args: argparse.Namespace) -> int:
12
+ raise NotImplementedYetError("enable")
13
+
14
+
15
+ def disable(ctx: AppContext, args: argparse.Namespace) -> int:
16
+ raise NotImplementedYetError("disable")
17
+
18
+
19
+ def addon(ctx: AppContext, args: argparse.Namespace) -> int:
20
+ action = getattr(args, "addon_action", None) or "addon"
21
+ raise NotImplementedYetError(f"addon {action}".strip())
@@ -0,0 +1,16 @@
1
+ """``aap-demo completion`` — phase 0 stub.
2
+
3
+ bash/zsh completion is backed by argcomplete over the argparse parser; fish and
4
+ PowerShell get hand-written scripts shipped as package data (design §3.1).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+
11
+ from aap_demo.core.context import AppContext
12
+ from aap_demo.core.errors import NotImplementedYetError
13
+
14
+
15
+ def completion(ctx: AppContext, args: argparse.Namespace) -> int:
16
+ raise NotImplementedYetError("completion")
aap_demo/cli/day2.py ADDED
@@ -0,0 +1,17 @@
1
+ """Day-2 playbook commands — phase 0 stubs (the day2 package lands in phase 6b).
2
+
3
+ Named ``day2`` rather than ``playbooks`` because ``cli/ops.py`` already carries
4
+ idle/ssh/config; the user-facing noun everywhere is still "playbooks".
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+
11
+ from aap_demo.core.context import AppContext
12
+ from aap_demo.core.errors import NotImplementedYetError
13
+
14
+
15
+ def playbooks(ctx: AppContext, args: argparse.Namespace) -> int:
16
+ action = getattr(args, "playbooks_action", None) or ""
17
+ raise NotImplementedYetError(f"playbooks {action}".strip())
aap_demo/cli/gui.py ADDED
@@ -0,0 +1,12 @@
1
+ """``aap-demo gui`` — phase 0 stub (the GUI lands in phase 7)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from aap_demo.core.context import AppContext
8
+ from aap_demo.core.errors import NotImplementedYetError
9
+
10
+
11
+ def gui(ctx: AppContext, args: argparse.Namespace) -> int:
12
+ raise NotImplementedYetError("gui")
@@ -0,0 +1,156 @@
1
+ """Cluster and AAP lifecycle commands (design §12.4).
2
+
3
+ ``deploy``, ``clean``, ``redeploy``, ``setup``, ``create`` and ``destroy`` are
4
+ implemented. ``redeploy-all``, ``start``, ``stop`` and ``repair`` stay stubs.
5
+ The command functions are thin: every decision lives in ``cluster/``, which
6
+ is what lets the GUI reach the same behavior without importing ``cli/`` (§2.2).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import time
13
+ from typing import Callable
14
+
15
+ from aap_demo.cluster import aap as aap_mod
16
+ from aap_demo.cluster import bootstrap as bootstrap_mod
17
+ from aap_demo.cluster import deploy as deploy_mod
18
+ from aap_demo.cluster import runtime as runtime_mod
19
+ from aap_demo.cluster import status as status_mod
20
+ from aap_demo.core import prompts
21
+ from aap_demo.core.context import AppContext
22
+ from aap_demo.core.errors import NotImplementedYetError
23
+ from aap_demo.core.output import is_structured, render
24
+ from aap_demo.infra import crc as infra_crc
25
+
26
+ #: ``read -t 10`` before the destructive clean (aap-demo.sh:748).
27
+ CLEAN_CONFIRM_SECONDS = 10
28
+
29
+
30
+ def create(ctx: AppContext, args: argparse.Namespace) -> int:
31
+ """Ports ``aap-demo create`` for the MicroShift preset."""
32
+ del args
33
+ bootstrap_mod.create(ctx)
34
+ return 0
35
+
36
+
37
+ def deploy(ctx: AppContext, args: argparse.Namespace) -> int:
38
+ """Ports ``cmd_deploy`` (aap-demo.sh:2018).
39
+
40
+ Exit status follows bash: reaching the CR and then timing out in
41
+ ``watch_aap`` is a failure (bash's ``return 1``), while the
42
+ already-exists short-circuit is a success.
43
+ """
44
+ deploy_mod.ensure_cluster_running(ctx)
45
+ result = deploy_mod.run(ctx, cr_name=getattr(args, "deploy__cr", None) or None)
46
+ if is_structured(ctx.output):
47
+ render(ctx.console, result.as_dict(), output=ctx.output)
48
+ return 0 if (result.ready or result.skipped_existing) else 1
49
+
50
+
51
+ def redeploy(ctx: AppContext, args: argparse.Namespace) -> int:
52
+ """Ports ``cmd_redeploy`` (aap-demo.sh:1902): clean, pause, deploy."""
53
+ _clean(ctx)
54
+ time.sleep(2)
55
+ ctx.console.out("")
56
+ ctx.console.out("Redeploying AAP...")
57
+ ctx.console.out("")
58
+ result = deploy_mod.run(ctx, cr_name=getattr(args, "deploy__cr", None) or None)
59
+ if is_structured(ctx.output):
60
+ render(ctx.console, result.as_dict(), output=ctx.output)
61
+ return 0 if result.ready else 1
62
+
63
+
64
+ def redeploy_all(ctx: AppContext, args: argparse.Namespace) -> int:
65
+ raise NotImplementedYetError("redeploy-all")
66
+
67
+
68
+ def setup(ctx: AppContext, args: argparse.Namespace) -> int:
69
+ """Ports ``cmd_setup`` (aap-demo.sh:2013), which is one ``echo`` and nothing else.
70
+
71
+ The help text still advertises "Run setup only (storage, coredns, mkcert)",
72
+ but that work moved into ``crc-create.sh`` long ago and the command has
73
+ been a pointer ever since. Porting the *advertised* behavior instead of
74
+ the actual behavior would invent a command that has never existed.
75
+ """
76
+ ctx.console.out("CRC setup is handled during 'aap-demo create'")
77
+ return 0
78
+
79
+
80
+ def start(ctx: AppContext, args: argparse.Namespace) -> int:
81
+ """Ports ``cmd_start``."""
82
+ del args
83
+ runtime_mod.start(ctx)
84
+ return 0
85
+
86
+
87
+ def stop(ctx: AppContext, args: argparse.Namespace) -> int:
88
+ """Ports ``cmd_stop``."""
89
+ del args
90
+ runtime_mod.stop(ctx)
91
+ return 0
92
+
93
+
94
+ def destroy(ctx: AppContext, args: argparse.Namespace) -> int:
95
+ """Ports ``cmd_destroy``. ``--reset`` clears the YAML config file."""
96
+ bootstrap_mod.destroy(ctx, reset=bool(getattr(args, "reset", False)))
97
+ return 0
98
+
99
+
100
+ def clean(ctx: AppContext, args: argparse.Namespace) -> int:
101
+ _clean(ctx)
102
+ return 0
103
+
104
+
105
+ def repair(ctx: AppContext, args: argparse.Namespace) -> int:
106
+ """Ports ``cmd_repair``."""
107
+ del args
108
+ runtime_mod.repair(ctx)
109
+ return 0
110
+
111
+
112
+ def _clean(ctx: AppContext, *, wait: Callable[[float], None] = None) -> None: # type: ignore[assignment]
113
+ """Ports ``_clean_operator``'s warning banner plus ``cluster/aap.py::teardown``."""
114
+ ns = ctx.namespace
115
+
116
+ from aap_demo.exec import kubectl
117
+
118
+ if not kubectl.exists(ctx.runner, "namespace", ns):
119
+ ctx.console.out(f"Namespace {ns} not found - nothing to clean")
120
+ return
121
+
122
+ ctx.console.out("")
123
+ ctx.console.out("WARNING: AAP CLEANUP - DESTRUCTIVE OPERATION!")
124
+ ctx.console.out("")
125
+ status_mod.show_cluster_info(ctx)
126
+ ctx.console.out("")
127
+
128
+ listing = ctx.runner.run(
129
+ ["kubectl", "get", "aap", "-n", ns, "--no-headers", "--request-timeout=2s"]
130
+ )
131
+ names = [line.split()[0] for line in (listing.stdout or "").splitlines() if line.strip()]
132
+ if names:
133
+ ctx.console.out(" AAP resources that will be DELETED:")
134
+ for name in names:
135
+ ctx.console.out(f" - {name}")
136
+ ctx.console.out("")
137
+
138
+ ctx.console.out(f"This will DELETE the namespace '{ns}' and all resources within it!")
139
+ ctx.console.out("")
140
+ prompts.timed_continue(ctx.console, seconds=CLEAN_CONFIRM_SECONDS, quiet=ctx.quiet, wait=wait)
141
+
142
+ import shutil
143
+
144
+ steps = ["Deleting the AAP instance", "Deleting the namespace", "Pruning unused images"]
145
+ if shutil.which("operator-sdk") is not None:
146
+ steps.insert(0, "Cleaning up OLM")
147
+ ctx.console.tasks(steps)
148
+ if not aap_mod.teardown(ctx, namespace=ns):
149
+ return
150
+ ctx.console.step("Pruning unused images")
151
+ pruned = infra_crc.prune_images(ctx)
152
+ if pruned:
153
+ ctx.console.checkpoint(f"Pruned {pruned} unused images")
154
+ else:
155
+ ctx.console.checkpoint("No unused images to prune")
156
+ ctx.console.success("AAP operator deployment removed")