Skip to content

Commit 3b4db87

Browse files
committed
only packages, read only mode, full reset
1 parent 66f8041 commit 3b4db87

File tree

4 files changed

+177
-122
lines changed

4 files changed

+177
-122
lines changed

src/redis_release/bht/args.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ class ReleaseArgs(BaseModel):
1010

1111
release_tag: str
1212
force_rebuild: List[str] = Field(default_factory=list)
13-
13+
only_packages: List[str] = Field(default_factory=list)

src/redis_release/bht/state.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ def __init__(
245245
storage: StateStorage,
246246
config: Config,
247247
args: "ReleaseArgs",
248+
read_only: bool = False,
248249
):
249250
self.tag = args.release_tag
250251
self.storage = storage
@@ -253,8 +254,11 @@ def __init__(
253254
self.last_dump: Optional[str] = None
254255
self._state: Optional[ReleaseState] = None
255256
self._lock_acquired = False
257+
self.read_only = read_only
256258

257259
def __enter__(self) -> "StateSyncer":
260+
if self.read_only:
261+
return self
258262
"""Acquire lock when entering context."""
259263
if not self.storage.acquire_lock(self.tag):
260264
raise RuntimeError(f"Failed to acquire lock for tag: {self.tag}")
@@ -263,6 +267,8 @@ def __enter__(self) -> "StateSyncer":
263267
return self
264268

265269
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
270+
if self.read_only:
271+
return
266272
"""Release lock when exiting context."""
267273
if self._lock_acquired:
268274
self.storage.release_lock(self.tag)
@@ -273,7 +279,15 @@ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
273279
@property
274280
def state(self) -> ReleaseState:
275281
if self._state is None:
276-
loaded = self.load()
282+
loaded = None
283+
if self.args.force_rebuild and "all" in self.args.force_rebuild:
284+
logger.info(
285+
"Force rebuild 'all' enabled, using default state based on config"
286+
)
287+
loaded = self.default_state()
288+
else:
289+
loaded = self.load()
290+
277291
if loaded is None:
278292
self._state = self.default_state()
279293
else:
@@ -315,6 +329,8 @@ def load(self) -> Optional[ReleaseState]:
315329

316330
def sync(self) -> None:
317331
"""Save state to storage backend if changed since last sync."""
332+
if self.read_only:
333+
raise RuntimeError("Cannot sync read-only state")
318334
current_dump = self.state.model_dump_json(indent=2)
319335

320336
if current_dump != self.last_dump:

src/redis_release/bht/tree.py

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from py_trees.display import unicode_tree
1212
from py_trees.trees import BehaviourTree
1313
from py_trees.visitors import SnapshotVisitor
14-
from rich.pretty import pretty_repr
1514
from rich.text import Text
1615

1716
from ..config import Config
@@ -52,6 +51,125 @@
5251
logger = logging.getLogger(__name__)
5352

5453

54+
class TreeInspector:
55+
"""Inspector for creating and inspecting behavior tree branches and PPAs."""
56+
57+
# List of available branch/PPA names
58+
AVAILABLE_NAMES = [
59+
"workflow_success",
60+
"workflow_completion",
61+
"find_workflow",
62+
"trigger_workflow",
63+
"identify_target_ref",
64+
"download_artifacts",
65+
"extract_artifact_result",
66+
"workflow_complete_branch",
67+
"workflow_with_result_branch",
68+
"publish_workflow_branch",
69+
"build_workflow_branch",
70+
"demo_sequence",
71+
"demo_selector",
72+
]
73+
74+
def __init__(self, release_tag: str):
75+
"""Initialize TreeInspector.
76+
77+
Args:
78+
release_tag: Release tag for creating mock ReleaseMeta
79+
"""
80+
self.release_tag = release_tag
81+
82+
def get_names(self) -> List[str]:
83+
"""Get list of available branch/PPA names.
84+
85+
Returns:
86+
List of available names that can be passed to create_by_name()
87+
"""
88+
return self.AVAILABLE_NAMES.copy()
89+
90+
def create_by_name(self, name: str) -> Union[Selector, Sequence, Behaviour]:
91+
"""Create a branch or PPA by name.
92+
93+
Args:
94+
name: Name of the branch or PPA to create
95+
96+
Returns:
97+
The created behavior tree branch or PPA
98+
99+
Raises:
100+
ValueError: If the name is not found in the available branches
101+
"""
102+
if name not in self.AVAILABLE_NAMES:
103+
available = ", ".join(self.get_names())
104+
raise ValueError(f"Unknown name '{name}'. Available options: {available}")
105+
106+
# Create mock objects for PPA/branch creation
107+
workflow = Workflow(workflow_file="test.yml", inputs={})
108+
package_meta = PackageMeta(repo="redis/redis", ref="main")
109+
release_meta = ReleaseMeta(tag=self.release_tag)
110+
github_client = GitHubClientAsync(token="dummy")
111+
package = Package(
112+
meta=package_meta,
113+
build=workflow,
114+
publish=Workflow(workflow_file="publish.yml", inputs={}),
115+
)
116+
log_prefix = "test"
117+
118+
# Create and return the requested branch/PPA
119+
if name == "workflow_success":
120+
return create_workflow_success_ppa(workflow, log_prefix)
121+
elif name == "workflow_completion":
122+
return create_workflow_completion_ppa(
123+
workflow, package_meta, github_client, log_prefix
124+
)
125+
elif name == "find_workflow":
126+
return create_find_workflow_by_uuid_ppa(
127+
workflow, package_meta, github_client, log_prefix
128+
)
129+
elif name == "trigger_workflow":
130+
return create_trigger_workflow_ppa(
131+
workflow, package_meta, release_meta, github_client, log_prefix
132+
)
133+
elif name == "identify_target_ref":
134+
return create_identify_target_ref_ppa(
135+
package_meta, release_meta, github_client, log_prefix
136+
)
137+
elif name == "download_artifacts":
138+
return create_download_artifacts_ppa(
139+
workflow, package_meta, github_client, log_prefix
140+
)
141+
elif name == "extract_artifact_result":
142+
return create_extract_artifact_result_ppa(
143+
"test-artifact", workflow, package_meta, github_client, log_prefix
144+
)
145+
elif name == "workflow_complete_branch":
146+
return create_workflow_complete_tree_branch(
147+
workflow, package_meta, release_meta, github_client, ""
148+
)
149+
elif name == "workflow_with_result_branch":
150+
return create_workflow_with_result_tree_branch(
151+
"artifact", workflow, package_meta, release_meta, github_client, ""
152+
)
153+
elif name == "publish_workflow_branch":
154+
return create_publish_workflow_tree_branch(
155+
workflow,
156+
workflow,
157+
package_meta,
158+
release_meta,
159+
workflow,
160+
github_client,
161+
"",
162+
)
163+
elif name == "build_workflow_branch":
164+
return create_build_workflow_tree_branch(
165+
package, release_meta, package, github_client, ""
166+
)
167+
elif name == "demo_sequence":
168+
return create_sequence_branch()
169+
else: # name == "demo_selector"
170+
return create_selector_branch()
171+
172+
55173
async def async_tick_tock(tree: BehaviourTree, cutoff: int = 100) -> None:
56174
"""Drive Behaviour tree using async event loop
57175
@@ -98,6 +216,7 @@ def initialize_tree_and_state(
98216
config: Config,
99217
args: ReleaseArgs,
100218
storage: Optional[StateStorage] = None,
219+
read_only: bool = False,
101220
) -> Iterator[Tuple[BehaviourTree, StateSyncer]]:
102221
github_client = GitHubClientAsync(token=os.getenv("GITHUB_TOKEN"))
103222

@@ -109,9 +228,13 @@ def initialize_tree_and_state(
109228
storage=storage,
110229
config=config,
111230
args=args,
231+
read_only=read_only,
112232
) as state_syncer:
113233
root = create_root_node(
114-
state_syncer.state, state_syncer.default_state(), github_client
234+
state_syncer.state,
235+
state_syncer.default_state(),
236+
github_client,
237+
args.only_packages,
115238
)
116239
tree = BehaviourTree(root)
117240

@@ -149,14 +272,20 @@ def log_tree_state_with_markup(tree: BehaviourTree) -> None:
149272

150273

151274
def create_root_node(
152-
state: ReleaseState, default_state: ReleaseState, github_client: GitHubClientAsync
275+
state: ReleaseState,
276+
default_state: ReleaseState,
277+
github_client: GitHubClientAsync,
278+
only_packages: Optional[List[str]] = None,
153279
) -> Behaviour:
154280

155281
root = ParallelBarrier(
156282
"Redis Release",
157283
children=[],
158284
)
159285
for package_name, package in state.packages.items():
286+
if only_packages and package_name not in only_packages:
287+
logger.info(f"Skipping package {package_name} as it's not in only_packages")
288+
continue
160289
root.add_child(
161290
create_package_release_tree_branch(
162291
package,

0 commit comments

Comments
 (0)