Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define Pydantic model to representing Kubernetes manifest #546

Merged
merged 42 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c218e02
Introduce KPOps operation and manifest resources for deployment
raminqaf Nov 28, 2024
4a29cbd
Introduce KPOps operation and manifest resources for deployment
raminqaf Nov 28, 2024
7ccd04f
Propose refactoring for #541
disrupted Nov 28, 2024
834cd14
Replace `manifest` method with `manifest_deploy`
disrupted Nov 28, 2024
6b3a57a
Merge branch 'main' of github.com:bakdata/kpops into feat/add-operations
raminqaf Dec 2, 2024
be817c3
Merge branch 'feat/kubernetes-manifests' of github.com:bakdata/kpops …
raminqaf Dec 2, 2024
3ce5c95
Introduce KPOps operation and manifest resources for deployment
raminqaf Dec 2, 2024
56f72a1
Introduce KPOps operation and manifest resources for deployment
raminqaf Dec 2, 2024
48de4e2
Merge branch 'main' of github.com:bakdata/kpops into feat/add-operations
raminqaf Dec 2, 2024
af9b23b
Update files
raminqaf Dec 2, 2024
4d1cc1e
Update files
raminqaf Dec 2, 2024
c717002
Update files
raminqaf Dec 2, 2024
975b10e
fix init command
raminqaf Dec 2, 2024
7b47919
clean ups
raminqaf Dec 2, 2024
9fcd55f
clean ups
raminqaf Dec 2, 2024
aaed48d
clean ups
raminqaf Dec 2, 2024
ece6e9f
clean ups
raminqaf Dec 2, 2024
3cc771e
Update files
raminqaf Dec 2, 2024
a7e3c00
Update files
raminqaf Dec 2, 2024
0ec82e2
Update files
raminqaf Dec 2, 2024
af25f92
address reviews
raminqaf Dec 3, 2024
9e1938e
add tests
raminqaf Dec 3, 2024
9a4dc35
Update files
raminqaf Dec 4, 2024
64cc384
refactor argo sync wave
raminqaf Dec 4, 2024
0c62a53
Address reviews
raminqaf Dec 4, 2024
ab5b74e
Address reviews
raminqaf Dec 4, 2024
8838bfa
Define pydantic model to representig Kubernetes manifest
raminqaf Dec 4, 2024
f1ed671
Define pydantic model to representig Kubernetes manifest
raminqaf Dec 4, 2024
a24de23
Update files
raminqaf Dec 4, 2024
32ea4dc
Update files
raminqaf Dec 4, 2024
79e1af4
Update files
raminqaf Dec 4, 2024
b2b2983
Update files
raminqaf Dec 4, 2024
d894e57
Update files
raminqaf Dec 4, 2024
e67908e
Update files
raminqaf Dec 4, 2024
0a2e7c8
Update files
raminqaf Dec 4, 2024
c0fb6a9
Define pydantic model to representig Kubernetes manifest
raminqaf Dec 5, 2024
dd2adbd
Define pydantic model to representig Kubernetes manifest
raminqaf Dec 5, 2024
44aa293
Add PoC for custom serializer
disrupted Dec 5, 2024
e7011ff
Add tests
raminqaf Dec 5, 2024
b407171
Address reviews
raminqaf Dec 5, 2024
a7383e0
Address reviews
raminqaf Dec 5, 2024
cd28315
Update files
raminqaf Dec 5, 2024
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
4 changes: 2 additions & 2 deletions kpops/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from kpops.component_handlers.schema_handler.schema_handler import SchemaHandler
from kpops.component_handlers.topic.handler import TopicHandler
from kpops.component_handlers.topic.proxy_wrapper import ProxyWrapper
from kpops.components.base_components.models.resource import Resource
from kpops.components.common.kubernetes_model import KubernetesManifest
from kpops.config import KpopsConfig
from kpops.pipeline import (
Pipeline,
Expand Down Expand Up @@ -79,7 +79,7 @@ def manifest_deploy(
environment: str | None = None,
verbose: bool = True,
operation_mode: OperationMode = OperationMode.MANIFEST,
) -> Iterator[Resource]:
) -> Iterator[tuple[KubernetesManifest, ...]]:
pipeline = generate(
pipeline_path=pipeline_path,
dotenv=dotenv,
Expand Down
2 changes: 1 addition & 1 deletion kpops/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def deploy(
)
for resource in resources:
for rendered_manifest in resource:
print_yaml(rendered_manifest)
print_yaml(rendered_manifest.model_dump())


@app.command(help="Destroy pipeline steps")
Expand Down
8 changes: 3 additions & 5 deletions kpops/component_handlers/helm_wrapper/helm.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@
RepoAuthFlags,
Version,
)
from kpops.component_handlers.kubernetes.model import KubernetesManifest
from kpops.components.common.kubernetes_model import KubernetesManifest
disrupted marked this conversation as resolved.
Show resolved Hide resolved

if TYPE_CHECKING:
from collections.abc import Iterable, Iterator

from kpops.components.base_components.models.resource import Resource


log = logging.getLogger("Helm")

Expand Down Expand Up @@ -162,7 +160,7 @@ def template(
namespace: str,
values: dict[str, Any],
flags: HelmTemplateFlags | None = None,
) -> Resource:
) -> tuple[KubernetesManifest, ...]:
"""From Helm: Render chart templates locally and display the output.

Any values that would normally be looked up or retrieved in-cluster will
Expand Down Expand Up @@ -193,7 +191,7 @@ def template(
command.extend(flags.to_command())
output = self.__execute(command)
manifests = KubernetesManifest.from_yaml(output)
return list(manifests)
return tuple(manifests)

def get_manifest(self, release_name: str, namespace: str) -> Iterable[HelmTemplate]:
command = [
Expand Down
17 changes: 10 additions & 7 deletions kpops/component_handlers/helm_wrapper/helm_diff.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import logging
from collections.abc import Iterable, Iterator
from typing import Any

from kpops.component_handlers.helm_wrapper.model import HelmDiffConfig, HelmTemplate
from kpops.component_handlers.kubernetes.model import KubernetesManifest
from kpops.utils.dict_differ import Change, render_diff

log = logging.getLogger("HelmDiff")
Expand All @@ -16,7 +16,7 @@ def __init__(self, config: HelmDiffConfig) -> None:
def calculate_changes(
current_release: Iterable[HelmTemplate],
new_release: Iterable[HelmTemplate],
) -> Iterator[Change[KubernetesManifest, KubernetesManifest]]:
) -> Iterator[Change[dict[str, Any], dict[str, Any]]]:
raminqaf marked this conversation as resolved.
Show resolved Hide resolved
"""Compare 2 releases and generate a Change object for each difference.

:param current_release: Iterable containing HelmTemplate objects for the current release
Expand All @@ -32,13 +32,16 @@ def calculate_changes(
# get corresponding dry-run release
new_resource = new_release_index.pop(current_resource.filepath, None)
yield Change(
current_resource.manifest,
new_resource.manifest if new_resource else KubernetesManifest(),
current_resource.manifest.model_dump(),
new_resource.manifest.model_dump() if new_resource else {},
raminqaf marked this conversation as resolved.
Show resolved Hide resolved
)

# collect added files
for new_resource in new_release_index.values():
yield Change(KubernetesManifest(), new_resource.manifest)
yield Change(
{},
new_resource.manifest.model_dump(mode="json"),
)

def log_helm_diff(
self,
Expand All @@ -48,8 +51,8 @@ def log_helm_diff(
) -> None:
for change in self.calculate_changes(current_release, new_release):
if diff := render_diff(
change.old_value.data,
change.new_value.data,
change.old_value,
change.new_value,
ignore=self.config.ignore,
):
logger.info("\n" + diff)
2 changes: 1 addition & 1 deletion kpops/component_handlers/helm_wrapper/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing_extensions import override

from kpops.component_handlers.helm_wrapper.exception import ParseError
from kpops.component_handlers.kubernetes.model import KubernetesManifest
from kpops.components.common.kubernetes_model import KubernetesManifest
from kpops.utils.docstring import describe_attr
from kpops.utils.pydantic import DescConfigModel

Expand Down
2 changes: 1 addition & 1 deletion kpops/component_handlers/helm_wrapper/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from kpops.component_handlers.kubernetes.model import K8S_LABEL_MAX_LEN
from kpops.component_handlers.kubernetes.utils import trim
from kpops.components.common.kubernetes_model import K8S_LABEL_MAX_LEN

RELEASE_NAME_MAX_LEN = 53

Expand Down
30 changes: 0 additions & 30 deletions kpops/component_handlers/kubernetes/model.py

This file was deleted.

8 changes: 5 additions & 3 deletions kpops/components/base_components/helm_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@
create_helm_name_override,
create_helm_release_name,
)
from kpops.component_handlers.kubernetes.model import K8S_LABEL_MAX_LEN
from kpops.components.base_components.kubernetes_app import (
KubernetesApp,
KubernetesAppValues,
)
from kpops.components.base_components.models.resource import Resource
from kpops.components.common.kubernetes_model import (
K8S_LABEL_MAX_LEN,
KubernetesManifest,
)
from kpops.config import get_config
from kpops.manifests.argo import ArgoSyncWave, enrich_annotations
from kpops.utils.colorify import magentaify
Expand Down Expand Up @@ -144,7 +146,7 @@ def template_flags(self) -> HelmTemplateFlags:
)

@override
def manifest_deploy(self) -> Resource:
def manifest_deploy(self) -> tuple[KubernetesManifest, ...]:
values = self.to_helm_values()
if get_config().operation_mode is OperationMode.ARGO:
sync_wave = ArgoSyncWave(sync_wave=1)
Expand Down
5 changes: 0 additions & 5 deletions kpops/components/base_components/models/resource.py
Original file line number Diff line number Diff line change
@@ -1,5 +0,0 @@
from collections.abc import Mapping
from typing import Any, TypeAlias

# representation of final resource for component, e.g. a list of Kubernetes manifests
Resource: TypeAlias = list[Mapping[str, Any]]
18 changes: 9 additions & 9 deletions kpops/components/base_components/pipeline_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
FromTopic,
InputTopicTypes,
)
from kpops.components.base_components.models.resource import Resource
from kpops.components.base_components.models.to_section import (
ToSection,
)
from kpops.components.common.kubernetes_model import KubernetesManifest
from kpops.components.common.topic import (
KafkaTopic,
OutputTopicTypes,
Expand Down Expand Up @@ -229,21 +229,21 @@ def inflate(self) -> list[PipelineComponent]:
"""
return [self]

def manifest_deploy(self) -> Resource:
def manifest_deploy(self) -> tuple[KubernetesManifest, ...]:
"""Render Kubernetes manifests for deploy."""
return []
return ()

def manifest_destroy(self) -> Resource:
def manifest_destroy(self) -> tuple[KubernetesManifest, ...]:
"""Render Kubernetes manifests resources for destroy."""
return []
return ()

def manifest_reset(self) -> Resource:
def manifest_reset(self) -> tuple[KubernetesManifest, ...]:
"""Render Kubernetes manifests resources for reset."""
return []
return ()

def manifest_clean(self) -> Resource:
def manifest_clean(self) -> tuple[KubernetesManifest, ...]:
"""Render Kubernetes manifests resources for clean."""
return []
return ()

async def deploy(self, dry_run: bool) -> None:
"""Deploy component, e.g. to Kubernetes cluster.
Expand Down
79 changes: 77 additions & 2 deletions kpops/components/common/kubernetes_model.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,87 @@
import enum
from collections.abc import Iterator
from typing import Any

from pydantic import Field
import yaml
from pydantic import ConfigDict, Field
from typing_extensions import override

from kpops.utils.docstring import describe_attr
from kpops.utils.pydantic import DescConfigModel
from kpops.utils.pydantic import CamelCaseConfigModel, DescConfigModel

# Matches plain integer or numbers with valid suffixes: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#meaning-of-memory
MEMORY_PATTERN = r"^\d+([EPTGMk]|Ei|Pi|Ti|Gi|Mi|Ki)?$"
K8S_LABEL_MAX_LEN = 63


class ManagedFieldsEntry(CamelCaseConfigModel):
# Define this class based on its actual structure
raminqaf marked this conversation as resolved.
Show resolved Hide resolved
fields: dict[str, Any] | None = None


class OwnerReference(CamelCaseConfigModel):
# Define this class based on its actual structure
apiVersion: str
kind: str
name: str
uid: str
controller: bool | None = None
block_owner_deletion: bool | None = None


class ObjectMeta(CamelCaseConfigModel):
"""Metadata for all Kubernetes objects."""

annotations: dict[str, str] | None = None
creation_timestamp: str | None = Field(
None, description="Timestamp in RFC3339 format"
)
deletion_grace_period_seconds: int | None = None
deletion_timestamp: str | None = Field(
None, description="Timestamp in RFC3339 format"
)
finalizers: list[str] | None = None
generate_name: str | None = None
generation: int | None = None
labels: dict[str, str] | None = None
managed_fields: list[ManagedFieldsEntry] | None = None
name: str | None = None
namespace: str | None = None
owner_references: list[OwnerReference] | None = None
resource_version: str | None = None
self_link: str | None = Field(
None,
description="Deprecated field, not populated by Kubernetes in modern versions",
)
uid: str | None = None

model_config = ConfigDict(extra="allow")


class KubernetesManifest(CamelCaseConfigModel):
api_version: str
kind: str
metadata: ObjectMeta

model_config = ConfigDict(extra="allow")

@classmethod
def from_yaml(
cls, /, content: str
) -> Iterator["KubernetesManifest"]: # TODO: typing.Self for Python 3.11+
manifests: Iterator[dict[str, Any]] = yaml.load_all(content, yaml.Loader)
for manifest in manifests:
yield cls(**manifest)

@override
def model_dump(self, **_: Any) -> dict[str, Any]:
raminqaf marked this conversation as resolved.
Show resolved Hide resolved
return super().model_dump(
mode="json",
by_alias=True,
exclude_none=True,
exclude_defaults=True,
exclude_unset=True,
raminqaf marked this conversation as resolved.
Show resolved Hide resolved
)


class ServiceType(str, enum.Enum):
Expand Down
10 changes: 6 additions & 4 deletions kpops/components/streams_bootstrap/producer/producer_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

from kpops.api import OperationMode
from kpops.components.base_components.kafka_app import KafkaAppCleaner
from kpops.components.base_components.models.resource import Resource
from kpops.components.common.app_type import AppType
from kpops.components.common.kubernetes_model import KubernetesManifest
from kpops.components.common.topic import (
KafkaTopic,
OutputTopicTypes,
Expand Down Expand Up @@ -36,11 +36,12 @@ def helm_chart(self) -> str:
)

@override
def manifest_deploy(self) -> Resource:
def manifest_deploy(self) -> tuple[KubernetesManifest, ...]:
values = self.to_helm_values()
if get_config().operation_mode is OperationMode.ARGO:
post_delete = ArgoHook.POST_DELETE
values = enrich_annotations(values, post_delete.key, post_delete.value)

return self.helm.template(
self.helm_release_name,
self.helm_chart,
Expand Down Expand Up @@ -144,11 +145,12 @@ async def clean(self, dry_run: bool) -> None:
await super().clean(dry_run)
await self._cleaner.clean(dry_run)

def manifest_deploy(self) -> Resource:
@override
def manifest_deploy(self) -> tuple[KubernetesManifest, ...]:
manifests = super().manifest_deploy()
operation_mode = get_config().operation_mode

if operation_mode is OperationMode.ARGO:
manifests.extend(self._cleaner.manifest_deploy())
manifests = manifests + self._cleaner.manifest_deploy()

return manifests
12 changes: 6 additions & 6 deletions kpops/components/streams_bootstrap/streams/streams_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from kpops.component_handlers.kubernetes.pvc_handler import PVCHandler
from kpops.components.base_components.helm_app import HelmApp
from kpops.components.base_components.kafka_app import KafkaAppCleaner
from kpops.components.base_components.models.resource import Resource
from kpops.components.common.app_type import AppType
from kpops.components.common.kubernetes_model import KubernetesManifest
from kpops.components.common.topic import KafkaTopic
from kpops.components.streams_bootstrap.base import (
StreamsBootstrap,
Expand Down Expand Up @@ -49,7 +49,7 @@ async def clean(self, dry_run: bool) -> None:
await self.clean_pvcs(dry_run)

@override
def manifest_deploy(self) -> Resource:
def manifest_deploy(self) -> tuple[KubernetesManifest, ...]:
values = self.to_helm_values()
if get_config().operation_mode is OperationMode.ARGO:
post_delete = ArgoHook.POST_DELETE
Expand Down Expand Up @@ -174,13 +174,13 @@ async def clean(self, dry_run: bool) -> None:
await self._cleaner.clean(dry_run)

@override
def manifest_deploy(self) -> Resource:
def manifest_deploy(self) -> tuple[KubernetesManifest, ...]:
manifests = super().manifest_deploy()
if get_config().operation_mode is OperationMode.ARGO:
manifests.extend(self._cleaner.manifest_deploy())
manifests = manifests + self._cleaner.manifest_deploy()

return manifests

@override
def manifest_clean(self) -> Resource:
return []
def manifest_clean(self) -> tuple[KubernetesManifest, ...]:
return ()
Loading