From 9fcf475338f76431483da984d0b7be9da76eef11 Mon Sep 17 00:00:00 2001 From: Thomas Neidhart Date: Mon, 29 Jan 2024 17:11:09 +0100 Subject: [PATCH] feat: exclude applying changes that require resolving of secrets; make applying of changes fail-safe --- otterdog/models/__init__.py | 8 ++++++++ otterdog/models/secret.py | 3 +++ otterdog/models/webhook.py | 3 +++ otterdog/operations/apply.py | 13 ++++++++++++- otterdog/operations/diff_operation.py | 25 +++++++++++++++---------- otterdog/webapp/tasks/apply_changes.py | 2 +- 6 files changed, 42 insertions(+), 12 deletions(-) diff --git a/otterdog/models/__init__.py b/otterdog/models/__init__.py index b473f900..91ad7f9e 100644 --- a/otterdog/models/__init__.py +++ b/otterdog/models/__init__.py @@ -108,6 +108,11 @@ def of_changes( async def apply(self, org_id: str, provider: GitHubProvider) -> None: await self.fn(self, org_id, provider) + def __repr__(self) -> str: + obj = self.expected_object if self.expected_object is not None else self.current_object + assert obj is not None + return f"{self.patch_type.name} - {obj.get_model_header(self.parent_object)}" + @dataclasses.dataclass class PatchContext(object): @@ -426,6 +431,9 @@ def to_model_dict(self, for_diff: bool = False, include_nested_models: bool = Fa return result + def contains_secrets(self) -> bool: + return False + def resolve_secrets(self, secret_resolver: Callable[[str], str]) -> None: pass diff --git a/otterdog/models/secret.py b/otterdog/models/secret.py index 457f8607..8fb3f48d 100644 --- a/otterdog/models/secret.py +++ b/otterdog/models/secret.py @@ -104,6 +104,9 @@ async def get_mapping_to_provider( field.name: S(field.name) for field in cls.provider_fields() if not is_unset(data.get(field.name, UNSET)) } + def contains_secrets(self) -> bool: + return True + def resolve_secrets(self, secret_resolver: Callable[[str], str]) -> None: secret_value = self.value if not is_unset(secret_value) and secret_value is not None: diff --git a/otterdog/models/webhook.py b/otterdog/models/webhook.py index affbc431..385535eb 100644 --- a/otterdog/models/webhook.py +++ b/otterdog/models/webhook.py @@ -143,6 +143,9 @@ async def get_mapping_to_provider( return mapping + def contains_secrets(self) -> bool: + return self.secret is not None + def resolve_secrets(self, secret_resolver: Callable[[str], str]) -> None: webhook_secret = self.secret if not is_unset(webhook_secret) and webhook_secret is not None: diff --git a/otterdog/operations/apply.py b/otterdog/operations/apply.py index 8287f2dd..302e3f68 100644 --- a/otterdog/operations/apply.py +++ b/otterdog/operations/apply.py @@ -27,11 +27,13 @@ def __init__( update_filter: str, delete_resources: bool, resolve_secrets: bool = True, + include_resources_with_secrets: bool = True, ): super().__init__(no_web_ui, update_webhooks, update_secrets, update_filter) self._force_processing = force_processing self._delete_resources = delete_resources self._resolve_secrets = resolve_secrets + self._include_resources_with_secrets = include_resources_with_secrets def init(self, config: OtterdogConfig, printer: IndentingPrinter) -> None: super().init(config, printer) @@ -40,6 +42,9 @@ def pre_execute(self) -> None: self.printer.println("Applying changes:") self.print_legend() + def include_resources_with_secrets(self) -> bool: + return self._include_resources_with_secrets + def resolve_secrets(self) -> bool: return self._resolve_secrets @@ -112,10 +117,16 @@ async def handle_finish(self, org_id: str, diff_status: DiffStatus, patches: lis if patch.patch_type == LivePatchType.REMOVE and not self._delete_resources: continue else: - await patch.apply(org_id, self.gh_client) + try: + await patch.apply(org_id, self.gh_client) + except RuntimeError as ex: + self.printer.println() + self.printer.print_error(f"failed to apply patch: {patch!r}\n{ex}") delete_snippet = "deleted" if self._delete_resources else "live resources ignored" + self.printer.println("\nDone.") + self.printer.println( f"\n{style('Executed plan', bright=True)}: {diff_status.additions} added, " f"{diff_status.differences} changed, " diff --git a/otterdog/operations/diff_operation.py b/otterdog/operations/diff_operation.py index 5ab86c23..8a81c76e 100644 --- a/otterdog/operations/diff_operation.py +++ b/otterdog/operations/diff_operation.py @@ -91,6 +91,9 @@ def gh_client(self) -> GitHubProvider: def verbose_output(self): return True + def include_resources_with_secrets(self) -> bool: + return True + def resolve_secrets(self) -> bool: return True @@ -146,8 +149,9 @@ def handle(patch: LivePatch) -> None: match patch.patch_type: case LivePatchType.ADD: assert patch.expected_object is not None - self.handle_add_object(github_id, patch.expected_object, patch.parent_object) - diff_status.additions += 1 + if self.include_resources_with_secrets() or not patch.expected_object.contains_secrets(): + self.handle_add_object(github_id, patch.expected_object, patch.parent_object) + diff_status.additions += 1 case LivePatchType.REMOVE: assert patch.current_object is not None @@ -159,14 +163,15 @@ def handle(patch: LivePatch) -> None: assert patch.current_object is not None assert patch.expected_object is not None - diff_status.differences += self.handle_modified_object( - github_id, - patch.changes, - False, - patch.current_object, - patch.expected_object, - patch.parent_object, - ) + if self.include_resources_with_secrets() or not patch.expected_object.contains_secrets(): + diff_status.differences += self.handle_modified_object( + github_id, + patch.changes, + False, + patch.current_object, + patch.expected_object, + patch.parent_object, + ) context = LivePatchContext( github_id, self.update_webhooks, self.update_secrets, self.update_filter, expected_org.settings diff --git a/otterdog/webapp/tasks/apply_changes.py b/otterdog/webapp/tasks/apply_changes.py index 7df4053c..c31ee7fb 100644 --- a/otterdog/webapp/tasks/apply_changes.py +++ b/otterdog/webapp/tasks/apply_changes.py @@ -87,10 +87,10 @@ async def apply_changes( update_filter="", delete_resources=True, resolve_secrets=False, + include_resources_with_secrets=False, ) operation.init(otterdog_config, printer) - # TODO: we need to exclude any change that requires credentials or the web ui await operation.execute(org_config) text = output.getvalue()