Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 36 additions & 34 deletions otterdog/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,6 @@ class LivePatchContext:
modified_org_workflow_settings: dict[str, Change] = dataclasses.field(default_factory=dict)


class LivePatchHandler(Protocol):
def __call__(self, patch: LivePatch) -> None: ...


@dataclasses.dataclass
class EmbeddedModelObject(ABC):
"""
Expand Down Expand Up @@ -710,41 +706,42 @@ def generate_live_patch(
current_object: MT | None,
parent_object: ModelObject | None,
context: LivePatchContext,
handler: LivePatchHandler,
) -> None:
if current_object is None:
expected_object = unwrap(expected_object)
handler(LivePatch.of_addition(expected_object, parent_object, expected_object.apply_live_patch))
return

if expected_object is None:
current_object = unwrap(current_object)
handler(LivePatch.of_deletion(current_object, parent_object, current_object.apply_live_patch))
return

modified_rule: dict[str, Change[Any]] = expected_object.get_difference_from(current_object)

if len(modified_rule) > 0:
handler(
LivePatch.of_changes(
expected_object,
current_object,
modified_rule,
parent_object,
False,
expected_object.apply_live_patch,
)
) -> LivePatch[MT] | None:
modified_model_entries: dict[str, Change[Any]] = (
expected_object.get_difference_from(current_object) if expected_object and current_object else {}
)
patch_type = (
LivePatchType.ADD
if current_object is None
else LivePatchType.REMOVE
if expected_object is None
else LivePatchType.CHANGE
if len(modified_model_entries) > 0
else None
)

if patch_type:
return LivePatch(
patch_type=patch_type,
expected_object=expected_object,
current_object=current_object,
changes=modified_model_entries if patch_type == LivePatchType.CHANGE else None,
parent_object=parent_object,
forced_update=False,
fn=expected_object.apply_live_patch if expected_object else current_object.apply_live_patch, # type: ignore
)

return None

@classmethod
def generate_live_patch_of_list(
cls,
cls: type[MT],
expected_objects: Sequence[MT],
current_objects: Sequence[MT],
parent_object: MT | None,
context: LivePatchContext,
handler: LivePatchHandler,
) -> None:
) -> list[LivePatch[MT]]:
patches: list[LivePatch[MT]] = []
expected_objects_by_key = associate_by_key(expected_objects, lambda x: x.get_key_value())
expected_objects_by_all_keys = multi_associate_by_key(expected_objects, lambda x: x.get_all_key_values())

Expand All @@ -763,19 +760,24 @@ def generate_live_patch_of_list(

if expected_object is None:
if current_object.include_existing_object_for_live_patch(context.org_id, parent_object):
cls.generate_live_patch(None, current_object, parent_object, context, handler)
if patch := cls.generate_live_patch(None, current_object, parent_object, context):
patches.append(patch)
continue

if expected_object.include_for_live_patch(context):
cls.generate_live_patch(expected_object, current_object, parent_object, context, handler)
if patch := cls.generate_live_patch(expected_object, current_object, parent_object, context):
patches.append(patch)

for k in expected_object.get_all_key_values():
expected_objects_by_all_keys.pop(k)
expected_objects_by_key.pop(expected_object.get_key_value())

for _, expected_object in expected_objects_by_key.items():
if expected_object.include_for_live_patch(context):
cls.generate_live_patch(expected_object, None, parent_object, context, handler)
if patch := cls.generate_live_patch(expected_object, None, parent_object, context):
patches.append(patch)

return patches

@classmethod
@abstractmethod
Expand Down
33 changes: 17 additions & 16 deletions otterdog/models/custom_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
FailureType,
LivePatch,
LivePatchContext,
LivePatchHandler,
LivePatchType,
ModelObject,
ValidationContext,
Expand Down Expand Up @@ -172,16 +171,17 @@ def generate_live_patch(
current_object: CustomProperty | None,
parent_object: ModelObject | None,
context: LivePatchContext,
handler: LivePatchHandler,
) -> None:
) -> LivePatch[CustomProperty] | None:
if current_object is None:
expected_object = unwrap(expected_object)
handler(LivePatch.of_addition(expected_object, parent_object, expected_object.apply_live_patch))
return
return LivePatch(
LivePatchType.ADD, expected_object, None, None, parent_object, False, expected_object.apply_live_patch
)

if expected_object is None:
handler(LivePatch.of_deletion(current_object, parent_object, current_object.apply_live_patch))
return
return LivePatch(
LivePatchType.REMOVE, None, current_object, None, parent_object, False, current_object.apply_live_patch
)

modified_property: dict[str, Change[Any]] = expected_object.get_difference_from(current_object)

Expand All @@ -192,17 +192,18 @@ def generate_live_patch(
f"{expected_object.get_model_header(parent_object)} which is not supported."
)

handler(
LivePatch.of_changes(
expected_object,
current_object,
modified_property,
parent_object,
False,
expected_object.apply_live_patch,
)
return LivePatch(
LivePatchType.CHANGE,
expected_object,
current_object,
modified_property,
parent_object,
False,
expected_object.apply_live_patch,
)

return None

@classmethod
async def apply_live_patch(cls, patch: LivePatch[CustomProperty], org_id: str, provider: GitHubProvider) -> None:
match patch.patch_type:
Expand Down
54 changes: 37 additions & 17 deletions otterdog/models/github_organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
from otterdog.logging import get_logger
from otterdog.models import (
FailureType,
LivePatch,
LivePatchContext,
LivePatchHandler,
ModelObject,
PatchContext,
ValidationContext,
Expand All @@ -46,7 +46,7 @@
from otterdog.models.repo_workflow_settings import RepositoryWorkflowSettings
from otterdog.models.repository import Repository
from otterdog.models.team import Team
from otterdog.utils import IndentingPrinter, associate_by_key, debug_times, jsonnet_evaluate_file
from otterdog.utils import IndentingPrinter, associate_by_key, debug_times, is_set_and_valid, jsonnet_evaluate_file

if TYPE_CHECKING:
from collections.abc import AsyncIterator, Callable, Iterator
Expand Down Expand Up @@ -472,26 +472,46 @@ def to_jsonnet(self, config: JsonnetConfig, context: PatchContext) -> str:
return output.getvalue()

def generate_live_patch(
self, current_organization: GitHubOrganization, context: LivePatchContext, handler: LivePatchHandler
) -> None:
OrganizationRole.generate_live_patch_of_list(self.roles, current_organization.roles, None, context, handler)
Team.generate_live_patch_of_list(self.teams, current_organization.teams, None, context, handler)
OrganizationSettings.generate_live_patch(self.settings, current_organization.settings, None, context, handler)
OrganizationWebhook.generate_live_patch_of_list(
self.webhooks, current_organization.webhooks, None, context, handler
self,
current_organization: GitHubOrganization,
context: LivePatchContext,
) -> list[LivePatch]:
patches: list[LivePatch] = []
patches.extend(
OrganizationRole.generate_live_patch_of_list(self.roles, current_organization.roles, None, context)
)
OrganizationSecret.generate_live_patch_of_list(
self.secrets, current_organization.secrets, None, context, handler
patches.extend(Team.generate_live_patch_of_list(self.teams, current_organization.teams, None, context))
if settings_patch := OrganizationSettings.generate_live_patch(
self.settings, current_organization.settings, None, context
):
patches.append(settings_patch)
if is_set_and_valid(self.settings.custom_properties):
patches.extend(
CustomProperty.generate_live_patch_of_list(
self.settings.custom_properties,
current_organization.settings.custom_properties,
self.settings,
context,
)
)
patches.extend(
OrganizationWebhook.generate_live_patch_of_list(self.webhooks, current_organization.webhooks, None, context)
)
OrganizationVariable.generate_live_patch_of_list(
self.variables, current_organization.variables, None, context, handler
patches.extend(
OrganizationSecret.generate_live_patch_of_list(self.secrets, current_organization.secrets, None, context)
)
patches.extend(
OrganizationVariable.generate_live_patch_of_list(
self.variables, current_organization.variables, None, context
)
)
OrganizationRuleset.generate_live_patch_of_list(
self.rulesets, current_organization.rulesets, None, context, handler
patches.extend(
OrganizationRuleset.generate_live_patch_of_list(self.rulesets, current_organization.rulesets, None, context)
)
Repository.generate_live_patch_of_list(
self.repositories, current_organization.repositories, None, context, handler
patches.extend(
Repository.generate_live_patch_of_list(self.repositories, current_organization.repositories, None, context)
)
return patches

@classmethod
def load_from_file(cls, github_id: str, config_file: str) -> GitHubOrganization:
Expand Down
32 changes: 11 additions & 21 deletions otterdog/models/organization_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
FailureType,
LivePatch,
LivePatchContext,
LivePatchHandler,
LivePatchType,
ModelObject,
PatchContext,
Expand Down Expand Up @@ -255,8 +254,7 @@ def generate_live_patch(
current_object: OrganizationSettings | None,
parent_object: ModelObject | None,
context: LivePatchContext,
handler: LivePatchHandler,
) -> None:
) -> LivePatch[OrganizationSettings] | None:
expected_object = unwrap(expected_object)
current_object = unwrap(current_object)

Expand All @@ -272,27 +270,19 @@ def generate_live_patch(
modified_settings.pop("default_code_security_configurations_disabled")

if len(modified_settings) > 0:
handler(
LivePatch.of_changes(
expected_object,
current_object,
modified_settings,
parent_object,
False,
cls.apply_live_patch,
)
patch = LivePatch.of_changes(
expected_object,
current_object,
modified_settings,
parent_object,
False,
cls.apply_live_patch,
)
else:
patch = None

context.modified_org_settings = modified_settings

if is_set_and_valid(expected_object.custom_properties):
CustomProperty.generate_live_patch_of_list(
expected_object.custom_properties,
current_object.custom_properties,
expected_object,
context,
handler,
)
return patch

@classmethod
async def apply_live_patch(
Expand Down
Loading
Loading