From 8ca1e34d4a34eb877e92a9c538828f86e000ee35 Mon Sep 17 00:00:00 2001 From: David Morrison <drmorr@appliedcomputing.io> Date: Thu, 25 Jan 2024 15:38:35 -0800 Subject: [PATCH] compute pod recreation for deployments --- fireconfig/__init__.py | 6 +++--- fireconfig/output.py | 10 +++++----- fireconfig/plan.py | 38 ++++++++++++++++++++++++++++---------- fireconfig/subgraph.py | 9 +++++---- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/fireconfig/__init__.py b/fireconfig/__init__.py index aa11bd5..1dada0a 100644 --- a/fireconfig/__init__.py +++ b/fireconfig/__init__.py @@ -63,8 +63,8 @@ def compile(pkgs: T.Dict[str, T.List[AppPackage]], dag_filename: T.Optional[str] for obj in DependencyGraph(app.node).root.outbound: walk_dep_graph(obj, subgraphs) - diff = compute_diff(app) - resource_changes = get_resource_changes(diff) + diff, kinds = compute_diff(app) + resource_changes = get_resource_changes(diff, kinds) try: find_deleted_nodes(subgraphs, resource_changes, dag_filename) @@ -74,6 +74,6 @@ def compile(pkgs: T.Dict[str, T.List[AppPackage]], dag_filename: T.Optional[str] graph_str = format_mermaid_graph(subgraph_dag, subgraphs, dag_filename, resource_changes) diff_str = format_diff(resource_changes) - app.synth() + # app.synth() return graph_str, diff_str diff --git a/fireconfig/output.py b/fireconfig/output.py index fa74538..7b2bc82 100644 --- a/fireconfig/output.py +++ b/fireconfig/output.py @@ -12,9 +12,9 @@ from fireconfig.subgraph import ChartSubgraph -def _format_node_label(n: str, ty: str) -> str: - name = n.split("/")[-1] - return f" {n}[<b>{ty}</b><br>{name}]\n" +def _format_node_label(node: str, kind: str) -> str: + name = node.split("/")[-1] + return f" {node}[<b>{kind}</b><br>{name}]\n" def format_mermaid_graph( @@ -34,8 +34,8 @@ def format_mermaid_graph( for chart, sg in subgraphs.items(): mermaid += f"subgraph {chart}\n" mermaid += " direction LR\n" - for n, ty in sg.nodes(): - mermaid += _format_node_label(n, ty) + for n, k in sg.nodes(): + mermaid += _format_node_label(n, k) for s, e in sg.edges(): mermaid += f" {s}--->{e}\n" diff --git a/fireconfig/plan.py b/fireconfig/plan.py index df3f930..f23c0f6 100644 --- a/fireconfig/plan.py +++ b/fireconfig/plan.py @@ -30,6 +30,7 @@ class ResourceState(Enum): ChangedWithPodRecreate = "#cb4" Added = "#283" Removed = "#e67" + Unknown = "#f00" class ResourceChanges: @@ -45,14 +46,29 @@ def state(self) -> ResourceState: def changes(self) -> T.List[ChangeTuple]: return self._changes - def update_state(self, change_type: str, path: str): - if self._state not in {ResourceState.Unchanged, ResourceState.Changed}: + def update_state(self, change_type: str, path: str, kind: T.Optional[str]): + if self._state in {ResourceState.Added, ResourceState.Removed}: return - if change_type == "dictionary_item_removed": - self._state = ResourceState.Removed if path == "root" else ResourceState.Changed - elif change_type == "dictionary_item_added": - self._state = ResourceState.Added if path == "root" else ResourceState.Changed + if path == "root": + if change_type == "dictionary_item_removed": + self._state = ResourceState.Removed + elif change_type == "dictionary_item_added": + self._state = ResourceState.Added + else: + self._state = ResourceState.Unknown + elif self._state == ResourceState.ChangedWithPodRecreate: + return + elif kind == "Deployment": + # TODO - this is obviously incomplete, it will not detect all cases + # when pod recreation happens + if ( + path.startswith("root['spec']['template']['spec']") + or path.startswith("root['spec']['selector']") + ): + self._state = ResourceState.ChangedWithPodRecreate + else: + self._state = ResourceState.Changed else: self._state = ResourceState.Changed @@ -60,7 +76,8 @@ def add_change(self, path: str, r1: T.Union[T.Mapping, notpresent], r2: T.Union[ self._changes.append((path, r1, r2)) -def compute_diff(app: App) -> T.Mapping[str, T.Any]: +def compute_diff(app: App) -> T.Tuple[T.Mapping[str, T.Any], T.Mapping[str, str]]: + kinds = {} old_defs = {} for filename in glob(f"{app.outdir}/*{app.output_file_extension}"): with open(filename) as f: @@ -77,8 +94,9 @@ def compute_diff(app: App) -> T.Mapping[str, T.Any]: for new_obj in chart.api_objects: node_id = owned_name(new_obj) new_defs[node_id] = new_obj.to_json() + kinds[node_id] = new_obj.kind - return DeepDiff(old_defs, new_defs, view="tree") + return DeepDiff(old_defs, new_defs, view="tree"), kinds def walk_dep_graph(v: DependencyVertex, subgraphs: T.Mapping[str, ChartSubgraph]): @@ -98,13 +116,13 @@ def walk_dep_graph(v: DependencyVertex, subgraphs: T.Mapping[str, ChartSubgraph] walk_dep_graph(dep, subgraphs) -def get_resource_changes(diff: T.Mapping[str, T.Any]) -> T.Mapping[str, ResourceChanges]: +def get_resource_changes(diff: T.Mapping[str, T.Any], kinds: T.Mapping[str, str]) -> T.Mapping[str, ResourceChanges]: resource_changes: T.MutableMapping[str, ResourceChanges] = defaultdict(lambda: ResourceChanges()) for change_type, items in diff.items(): for i in items: root_item = i.path(output_format="list")[0] path = re.sub(r"\[" + f"'{root_item}'" + r"\]", "", i.path()) - resource_changes[root_item].update_state(change_type, path) + resource_changes[root_item].update_state(change_type, path, kinds.get(root_item)) resource_changes[root_item].add_change(path, i.t1, i.t2) return resource_changes diff --git a/fireconfig/subgraph.py b/fireconfig/subgraph.py index daab37f..55e16b9 100644 --- a/fireconfig/subgraph.py +++ b/fireconfig/subgraph.py @@ -11,12 +11,13 @@ class ChartSubgraph: def __init__(self, name: str) -> None: self._name = name self._dag: T.MutableMapping[str, T.List[str]] = defaultdict(list) - self._resource_types: T.MutableMapping[str, str] = {} + self._kinds: T.MutableMapping[str, str] = {} self._deleted_lines: T.Set[str] = set() def add_node(self, v: DependencyVertex) -> str: - name = owned_name(T.cast(ApiObject, v.value)) - self._resource_types[name] = type(v.value).__name__.replace("Kube", "") + obj = T.cast(ApiObject, v.value) + name = owned_name(obj) + self._kinds[name] = obj.kind self._dag[name] return name @@ -29,7 +30,7 @@ def add_deleted_line(self, l: str): self._deleted_lines.add(l) def nodes(self) -> T.List[T.Tuple[str, str]]: - return [(n, self._resource_types[n]) for n in self._dag.keys()] + return [(n, self._kinds[n]) for n in self._dag.keys()] def edges(self) -> T.List[T.Tuple[str, str]]: return [(s, e) for s, l in self._dag.items() for e in l]