From 7e9f49a39eda2de41a7bef8f7ee03b9c148afc01 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Wed, 10 Jun 2026 16:40:44 -0700 Subject: [PATCH] Match all resources of a kind with a bare resource selector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit compose-model-deployment and compose-model-cache need to ask Crossplane for "all InferenceClusters" and "all ModelReplicas". Crossplane's required resources API used to reject a ResourceSelector with neither match_name nor match_labels, so both functions required a presence label on every resource — modelplane.ai/cluster=true on InferenceClusters and modelplane.ai/replica=true on ModelReplicas — and matched on it. The cluster label was the worse of the two: a mandatory label with no semantic meaning that the platform team had to remember on every InferenceCluster, with no signal in the schema that it was required. crossplane/crossplane#7241 fixed the underlying issue in v2.2.1: a ResourceSelector with only apiVersion and kind set now matches all resources of that kind. function-sdk-python v0.14.0 then let require_resources build such a selector when called with neither match field. This bumps the SDK to v0.14.0, drops both presence labels, and matches all InferenceClusters (when no clusterSelector is set) and all ModelReplicas by calling require_resources with no match field. The package declares spec.crossplane.version '>=v2.2.1' so it won't install on a Crossplane without the fix, and the CRD apiDependency moves from the release-2.2 branch to the v2.3.2 tag. The ModelDeployment, ModelCache, and ModelService XRDs declared their matchLabels selectors as bare objects with x-kubernetes-preserve-unknown-fields, which let non-string label values through to the functions as dict[str, Any]. A label selector's values are always strings, so this types them as additionalProperties of type string — matching how the XRDs already declare labels and annotations — and drops the now-redundant str() coercion in compose-model-cache. Fixes #11. Signed-off-by: Nic Cope --- apis/modelcaches/definition.yaml | 3 +- apis/modeldeployments/definition.yaml | 3 +- apis/modelservices/definition.yaml | 3 +- crossplane-project.yaml | 7 ++++- examples/platform/inference-cluster-eks.yaml | 4 --- .../platform/inference-cluster-existing.yaml | 1 - examples/platform/inference-cluster-gke.yaml | 6 ++-- examples/qwen-demo/03-cluster.yaml | 3 -- functions/compose-eks-cluster/pyproject.toml | 2 +- functions/compose-gke-cluster/pyproject.toml | 2 +- .../compose-inference-class/pyproject.toml | 2 +- .../compose-inference-cluster/pyproject.toml | 2 +- .../compose-inference-gateway/pyproject.toml | 2 +- functions/compose-model-cache/function/fn.py | 13 +++----- functions/compose-model-cache/pyproject.toml | 2 +- .../compose-model-cache/tests/test_fn.py | 7 ++--- .../compose-model-deployment/function/fn.py | 24 +++++++-------- .../compose-model-deployment/pyproject.toml | 2 +- .../compose-model-deployment/tests/test_fn.py | 14 ++------- .../tests/test_scheduling.py | 1 - .../compose-model-endpoint/pyproject.toml | 2 +- .../compose-model-replica/pyproject.toml | 2 +- .../compose-model-service/pyproject.toml | 2 +- .../compose-serving-stack/pyproject.toml | 2 +- pyproject.toml | 2 +- schemas/.lock.json | 2 +- .../ai/modelplane/modelcache/v1alpha1.py | 4 +-- .../ai/modelplane/modeldeployment/v1alpha1.py | 4 +-- .../ai/modelplane/modelservice/v1alpha1.py | 4 +-- uv.lock | 30 +++++++++---------- 30 files changed, 67 insertions(+), 90 deletions(-) diff --git a/apis/modelcaches/definition.yaml b/apis/modelcaches/definition.yaml index c53459cf9..3ed1e9071 100644 --- a/apis/modelcaches/definition.yaml +++ b/apis/modelcaches/definition.yaml @@ -86,7 +86,8 @@ spec: properties: matchLabels: type: object - x-kubernetes-preserve-unknown-fields: true + additionalProperties: + type: string status: type: object properties: diff --git a/apis/modeldeployments/definition.yaml b/apis/modeldeployments/definition.yaml index 382785c8f..2457ae486 100644 --- a/apis/modeldeployments/definition.yaml +++ b/apis/modeldeployments/definition.yaml @@ -42,7 +42,8 @@ spec: properties: matchLabels: type: object - x-kubernetes-preserve-unknown-fields: true + additionalProperties: + type: string nodeSelector: type: object description: >- diff --git a/apis/modelservices/definition.yaml b/apis/modelservices/definition.yaml index c7df647ee..b7fbe55f3 100644 --- a/apis/modelservices/definition.yaml +++ b/apis/modelservices/definition.yaml @@ -44,7 +44,8 @@ spec: properties: matchLabels: type: object - x-kubernetes-preserve-unknown-fields: true + additionalProperties: + type: string status: type: object properties: diff --git a/crossplane-project.yaml b/crossplane-project.yaml index ef43f2179..1bad73a87 100644 --- a/crossplane-project.yaml +++ b/crossplane-project.yaml @@ -8,6 +8,11 @@ spec: license: Apache-2.0 description: An open-source AI inference platform built on Crossplane. architectures: [amd64, arm64] + # compose-model-deployment and compose-model-cache match all resources of a + # kind with a bare ResourceSelector, which Crossplane only honours from + # v2.2.1 (https://github.com/crossplane/crossplane/pull/7241). + crossplane: + version: '>=v2.2.1' schemas: languages: [python] functions: @@ -59,7 +64,7 @@ spec: - type: crd git: repository: https://github.com/crossplane/crossplane - ref: release-2.2 + ref: v2.3.2 path: cluster/crds - type: xpkg xpkg: diff --git a/examples/platform/inference-cluster-eks.yaml b/examples/platform/inference-cluster-eks.yaml index d54dc6431..9e6a6fdbc 100644 --- a/examples/platform/inference-cluster-eks.yaml +++ b/examples/platform/inference-cluster-eks.yaml @@ -16,9 +16,6 @@ # EFS CSI StorageClass on the cluster, then set cache.storageClassName # to its name (default 'modelplane-rwx-efs'). # -# The modelplane.ai/cluster label is required for the ModelDeployment -# scheduler to discover this cluster. -# # Delete this InferenceCluster with foreground cascading deletion for a # clean teardown: # @@ -36,7 +33,6 @@ kind: InferenceCluster metadata: name: eks-us-west labels: - modelplane.ai/cluster: "true" modelplane.ai/region: us-west spec: cluster: diff --git a/examples/platform/inference-cluster-existing.yaml b/examples/platform/inference-cluster-existing.yaml index fe5cc30c8..fadf74227 100644 --- a/examples/platform/inference-cluster-existing.yaml +++ b/examples/platform/inference-cluster-existing.yaml @@ -12,7 +12,6 @@ kind: InferenceCluster metadata: name: byo-us-east labels: - modelplane.ai/cluster: "true" modelplane.ai/region: us-east spec: cluster: diff --git a/examples/platform/inference-cluster-gke.yaml b/examples/platform/inference-cluster-gke.yaml index 5e3c40e99..2fb841f2e 100644 --- a/examples/platform/inference-cluster-gke.yaml +++ b/examples/platform/inference-cluster-gke.yaml @@ -10,15 +10,13 @@ # referencing an InferenceClass that describes the hardware shape and # how to provision it - need to be declared. # -# The modelplane.ai/cluster label is required for the ModelDeployment -# scheduler to discover this cluster. Add additional labels to control -# placement via clusterSelector. +# Add labels to this InferenceCluster to control which deployments land on it +# via a ModelDeployment's clusterSelector. apiVersion: modelplane.ai/v1alpha1 kind: InferenceCluster metadata: name: gke-us-central labels: - modelplane.ai/cluster: "true" modelplane.ai/region: us-central spec: cluster: diff --git a/examples/qwen-demo/03-cluster.yaml b/examples/qwen-demo/03-cluster.yaml index 0962f6026..9695eb5c9 100644 --- a/examples/qwen-demo/03-cluster.yaml +++ b/examples/qwen-demo/03-cluster.yaml @@ -11,7 +11,6 @@ kind: InferenceCluster metadata: name: demo-us-central labels: - modelplane.ai/cluster: "true" modelplane.ai/region: us-central spec: cluster: @@ -33,7 +32,6 @@ kind: InferenceCluster metadata: name: demo-us-east labels: - modelplane.ai/cluster: "true" modelplane.ai/region: us-east spec: cluster: @@ -55,7 +53,6 @@ kind: InferenceCluster metadata: name: demo-us-west labels: - modelplane.ai/cluster: "true" modelplane.ai/region: us-west spec: cluster: diff --git a/functions/compose-eks-cluster/pyproject.toml b/functions/compose-eks-cluster/pyproject.toml index a241fd23f..df1fcc12d 100644 --- a/functions/compose-eks-cluster/pyproject.toml +++ b/functions/compose-eks-cluster/pyproject.toml @@ -9,7 +9,7 @@ description = "Compose an EKS cluster with networking, node groups, and IAM role requires-python = ">=3.11,<3.14" license = "Apache-2.0" dependencies = [ - "crossplane-function-sdk-python>=0.13.0", + "crossplane-function-sdk-python>=0.14.0", "click>=8.1.0", "grpcio>=1.73.1", "crossplane-models", diff --git a/functions/compose-gke-cluster/pyproject.toml b/functions/compose-gke-cluster/pyproject.toml index 6c1146d7d..9f0e78f12 100644 --- a/functions/compose-gke-cluster/pyproject.toml +++ b/functions/compose-gke-cluster/pyproject.toml @@ -9,7 +9,7 @@ description = "Compose a GKE cluster with networking, node pools, and service ac requires-python = ">=3.11,<3.14" license = "Apache-2.0" dependencies = [ - "crossplane-function-sdk-python>=0.13.0", + "crossplane-function-sdk-python>=0.14.0", "click>=8.1.0", "grpcio>=1.73.1", "crossplane-models", diff --git a/functions/compose-inference-class/pyproject.toml b/functions/compose-inference-class/pyproject.toml index f06fcbe56..7c3f27f47 100644 --- a/functions/compose-inference-class/pyproject.toml +++ b/functions/compose-inference-class/pyproject.toml @@ -9,7 +9,7 @@ description = "Mark an InferenceClass as ready." requires-python = ">=3.11,<3.14" license = "Apache-2.0" dependencies = [ - "crossplane-function-sdk-python>=0.13.0", + "crossplane-function-sdk-python>=0.14.0", "click>=8.1.0", "grpcio>=1.73.1", "crossplane-models", diff --git a/functions/compose-inference-cluster/pyproject.toml b/functions/compose-inference-cluster/pyproject.toml index 227eb1edb..15b98595c 100644 --- a/functions/compose-inference-cluster/pyproject.toml +++ b/functions/compose-inference-cluster/pyproject.toml @@ -9,7 +9,7 @@ description = "Compose an InferenceCluster from a cluster source and serving sta requires-python = ">=3.11,<3.14" license = "Apache-2.0" dependencies = [ - "crossplane-function-sdk-python>=0.13.0", + "crossplane-function-sdk-python>=0.14.0", "click>=8.1.0", "grpcio>=1.73.1", "crossplane-models", diff --git a/functions/compose-inference-gateway/pyproject.toml b/functions/compose-inference-gateway/pyproject.toml index 6e9aa1879..087392c7b 100644 --- a/functions/compose-inference-gateway/pyproject.toml +++ b/functions/compose-inference-gateway/pyproject.toml @@ -9,7 +9,7 @@ description = "Compose the control plane routing gateway with Envoy Gateway and requires-python = ">=3.11,<3.14" license = "Apache-2.0" dependencies = [ - "crossplane-function-sdk-python>=0.13.0", + "crossplane-function-sdk-python>=0.14.0", "click>=8.1.0", "grpcio>=1.73.1", "crossplane-models", diff --git a/functions/compose-model-cache/function/fn.py b/functions/compose-model-cache/function/fn.py index 74aa970a7..b1f584125 100644 --- a/functions/compose-model-cache/function/fn.py +++ b/functions/compose-model-cache/function/fn.py @@ -44,12 +44,6 @@ # functions set this independently, so they are a contract — change together. REMOTE_NS = "default" -# The cluster-presence label every InferenceCluster carries; the matcher always -# includes it (an empty match_labels is dropped by protobuf). Mirrors -# compose-model-deployment's cluster matching. -LABEL_KEY_CLUSTER = "modelplane.ai/cluster" -LABEL_VALUE_CLUSTER = "true" - # Hydration container. python:3.11-slim has pip; we install huggingface_hub # at runtime. A Modelplane-owned image with the tool preinstalled is a # follow-up (#115). @@ -163,10 +157,11 @@ def resolve_inputs(self) -> bool: Crossplane re-calls the function once it's available. A resolved-but- empty match flows through (match_clusters() -> NoClusters condition). """ - match_labels = {LABEL_KEY_CLUSTER: LABEL_VALUE_CLUSTER} + # require_resources with no match field matches every InferenceCluster; + # narrow only when the user sets a clusterSelector. + match_labels = None if self.xr.spec.clusterSelector and self.xr.spec.clusterSelector.matchLabels: - match_labels.update({k: str(v) for k, v in self.xr.spec.clusterSelector.matchLabels.items()}) - + match_labels = dict(self.xr.spec.clusterSelector.matchLabels) response.require_resources( self.rsp, name="clusters", diff --git a/functions/compose-model-cache/pyproject.toml b/functions/compose-model-cache/pyproject.toml index e5801d906..ede12492b 100644 --- a/functions/compose-model-cache/pyproject.toml +++ b/functions/compose-model-cache/pyproject.toml @@ -9,7 +9,7 @@ description = "Stage a model onto an RWX PVC on every matched InferenceCluster." requires-python = ">=3.11,<3.14" license = "Apache-2.0" dependencies = [ - "crossplane-function-sdk-python>=0.12.0", + "crossplane-function-sdk-python>=0.14.0", "click>=8.1.0", "grpcio>=1.73.1", "crossplane-models", diff --git a/functions/compose-model-cache/tests/test_fn.py b/functions/compose-model-cache/tests/test_fn.py index 5fc957957..f42345d61 100644 --- a/functions/compose-model-cache/tests/test_fn.py +++ b/functions/compose-model-cache/tests/test_fn.py @@ -52,7 +52,7 @@ def _cluster_dict(name: str, pc: str, *, source: str = "GKE") -> dict: return { "apiVersion": "modelplane.ai/v1alpha1", "kind": "InferenceCluster", - "metadata": {"name": name, "labels": {"modelplane.ai/cluster": "true"}}, + "metadata": {"name": name}, "spec": {"cluster": {"source": source, **blocks[source]}}, "status": {"providerConfigRef": {"name": pc}}, } @@ -110,13 +110,12 @@ def _req( return req -# The function always requires every InferenceCluster carrying the cluster -# label, so every response echoes this selector under requirements. +# The function requires every InferenceCluster with a bare selector, so every +# response echoes this selector under requirements. _CLUSTERS_SELECTOR = fnv1.ResourceSelector( api_version="modelplane.ai/v1alpha1", kind="InferenceCluster", ) -_CLUSTERS_SELECTOR.match_labels.labels.update({"modelplane.ai/cluster": "true"}) # The hydration shell script the Job runs (no revision, no auth secret). _HYDRATE_CMD = ( diff --git a/functions/compose-model-deployment/function/fn.py b/functions/compose-model-deployment/function/fn.py index 975cbc81c..c4aa6d583 100644 --- a/functions/compose-model-deployment/function/fn.py +++ b/functions/compose-model-deployment/function/fn.py @@ -31,16 +31,14 @@ CONDITION_REASON_ALL_REPLICAS_READY = "AllReplicasReady" CONDITION_REASON_MODEL_STARTING = "ModelStarting" -# Label keys and values. These are coordination contracts: compose-model-replica -# and compose-model-service read labels that this function writes. +# Label keys stamped on the ModelReplicas and ModelEndpoints this function +# composes, identifying the deployment and cluster they belong to. _LABEL_CLUSTER = "modelplane.ai/cluster" -_LABEL_REPLICA = "modelplane.ai/replica" _LABEL_DEPLOYMENT = "modelplane.ai/deployment" # Per-cluster-local index distinguishing co-located replicas of one deployment. # Read back by the scheduler to reconstruct a replica's (cluster, index) # identity from observed state. Not an ordering - just a collision breaker. _LABEL_INDEX = "modelplane.ai/replica-index" -_LABEL_VALUE_TRUE = "true" # Scheme for gateway-facing URLs. Traffic between the control plane gateway @@ -116,27 +114,26 @@ def compose(self): def resolve_inputs(self): """Declare and fetch required resources. Returns False if critical inputs are missing.""" - # InferenceClusters are matched by the modelplane.ai/cluster=true - # label — a workaround for the empty match_labels protobuf bug. - cluster_match_labels: dict[str, str] = { - _LABEL_CLUSTER: _LABEL_VALUE_TRUE, - } + # Match all InferenceClusters by default — require_resources with no + # match field matches every resource of the kind — narrowing only when + # the user sets a clusterSelector. + clusters_match_labels = None if self.xr.spec.clusterSelector and self.xr.spec.clusterSelector.matchLabels: - cluster_match_labels.update(self.xr.spec.clusterSelector.matchLabels) - + clusters_match_labels = dict(self.xr.spec.clusterSelector.matchLabels) response.require_resources( self.rsp, name="clusters", api_version="modelplane.ai/v1alpha1", kind="InferenceCluster", - match_labels=cluster_match_labels, + match_labels=clusters_match_labels, ) + # Match all ModelReplicas (across all deployments) so the scheduler can + # account for capacity already consumed by other deployments. response.require_resources( self.rsp, name="all-replicas", api_version="modelplane.ai/v1alpha1", kind="ModelReplica", - match_labels={_LABEL_REPLICA: _LABEL_VALUE_TRUE}, ) cluster_dicts = request.get_required_resources(self.req, "clusters") @@ -213,7 +210,6 @@ def compose_replicas(self, matched): name=name.replica(self.xr.metadata.name, cluster_info), namespace=self.xr.metadata.namespace, labels={ - _LABEL_REPLICA: _LABEL_VALUE_TRUE, _LABEL_DEPLOYMENT: self.xr.metadata.name, _LABEL_CLUSTER: cluster_info.name, _LABEL_INDEX: str(cluster_info.index), diff --git a/functions/compose-model-deployment/pyproject.toml b/functions/compose-model-deployment/pyproject.toml index f63e35896..2ea0ec365 100644 --- a/functions/compose-model-deployment/pyproject.toml +++ b/functions/compose-model-deployment/pyproject.toml @@ -9,7 +9,7 @@ description = "Fan out a ModelDeployment to ModelReplicas and ModelEndpoints." requires-python = ">=3.11,<3.14" license = "Apache-2.0" dependencies = [ - "crossplane-function-sdk-python>=0.13.0", + "crossplane-function-sdk-python>=0.14.0", "click>=8.1.0", "grpcio>=1.73.1", "cel-python>=0.3.0", diff --git a/functions/compose-model-deployment/tests/test_fn.py b/functions/compose-model-deployment/tests/test_fn.py index a48b3c428..0b12d4b8c 100644 --- a/functions/compose-model-deployment/tests/test_fn.py +++ b/functions/compose-model-deployment/tests/test_fn.py @@ -112,7 +112,6 @@ def _cluster(name: str, *, ready: bool = True, address: str | None = "10.0.0.1", name="my-model-5ab63", namespace="ml-team", labels={ - "modelplane.ai/replica": "true", "modelplane.ai/deployment": "my-model", "modelplane.ai/cluster": "cluster-a", "modelplane.ai/replica-index": "0", @@ -141,11 +140,10 @@ def _cluster(name: str, *, ready: bool = True, address: str | None = "10.0.0.1", ), ).model_dump(exclude_none=True, mode="json") -# The requirements selectors every want echoes back. +# The requirements selectors every want echoes back. Both are bare selectors +# matching all resources of the kind. _CLUSTER_SEL = fnv1.ResourceSelector(api_version="modelplane.ai/v1alpha1", kind="InferenceCluster") -_CLUSTER_SEL.match_labels.labels.update({"modelplane.ai/cluster": "true"}) _REPLICA_SEL = fnv1.ResourceSelector(api_version="modelplane.ai/v1alpha1", kind="ModelReplica") -_REPLICA_SEL.match_labels.labels.update({"modelplane.ai/replica": "true"}) def _req(xr: dict, *, clusters: list[dict], replicas: list[dict] | None = None, observed: dict | None = None): @@ -282,7 +280,6 @@ async def test_compose(self) -> None: "name": "my-model-5ab63", "namespace": "ml-team", "labels": { - "modelplane.ai/replica": "true", "modelplane.ai/deployment": "my-model", "modelplane.ai/cluster": "cluster-a", "modelplane.ai/replica-index": "0", @@ -421,7 +418,6 @@ async def test_compose(self) -> None: "name": "my-model-5ab63", "namespace": "ml-team", "labels": { - "modelplane.ai/replica": "true", "modelplane.ai/deployment": "my-model", "modelplane.ai/cluster": "cluster-a", "modelplane.ai/replica-index": "0", @@ -452,7 +448,6 @@ async def test_compose(self) -> None: "name": "my-model-5ab63", "namespace": "ml-team", "labels": { - "modelplane.ai/replica": "true", "modelplane.ai/deployment": "my-model", "modelplane.ai/cluster": "cluster-a", "modelplane.ai/replica-index": "0", @@ -547,7 +542,6 @@ async def test_compose(self) -> None: "name": "my-model-5ab63", "namespace": "ml-team", "labels": { - "modelplane.ai/replica": "true", "modelplane.ai/deployment": "my-model", "modelplane.ai/cluster": "cluster-a", "modelplane.ai/replica-index": "0", @@ -621,7 +615,6 @@ async def test_compose(self) -> None: "name": "my-model-f0b76", "namespace": "ml-team", "labels": { - "modelplane.ai/replica": "true", "modelplane.ai/deployment": "my-model", "modelplane.ai/cluster": "cluster-b", "modelplane.ai/replica-index": "0", @@ -716,7 +709,6 @@ async def test_compose(self) -> None: "name": "my-model-5ab63", "namespace": "ml-team", "labels": { - "modelplane.ai/replica": "true", "modelplane.ai/deployment": "my-model", "modelplane.ai/cluster": "cluster-a", "modelplane.ai/replica-index": "0", @@ -812,7 +804,6 @@ async def test_compose(self) -> None: "name": "my-model-5ab63", "namespace": "ml-team", "labels": { - "modelplane.ai/replica": "true", "modelplane.ai/deployment": "my-model", "modelplane.ai/cluster": "cluster-a", "modelplane.ai/replica-index": "0", @@ -849,7 +840,6 @@ async def test_compose(self) -> None: "name": "my-model-609c5", "namespace": "ml-team", "labels": { - "modelplane.ai/replica": "true", "modelplane.ai/deployment": "my-model", "modelplane.ai/cluster": "cluster-a", "modelplane.ai/replica-index": "1", diff --git a/functions/compose-model-deployment/tests/test_scheduling.py b/functions/compose-model-deployment/tests/test_scheduling.py index 5034bdc31..4e782dd90 100644 --- a/functions/compose-model-deployment/tests/test_scheduling.py +++ b/functions/compose-model-deployment/tests/test_scheduling.py @@ -183,7 +183,6 @@ def _replica( name=f"{deployment_name}-{cluster_name}-{index}", namespace="ml-team", labels={ - "modelplane.ai/replica": "true", "modelplane.ai/deployment": deployment_name, "modelplane.ai/cluster": cluster_name, "modelplane.ai/replica-index": str(index), diff --git a/functions/compose-model-endpoint/pyproject.toml b/functions/compose-model-endpoint/pyproject.toml index b5f954850..9495319e6 100644 --- a/functions/compose-model-endpoint/pyproject.toml +++ b/functions/compose-model-endpoint/pyproject.toml @@ -9,7 +9,7 @@ description = "Compose an Envoy Gateway Backend from a ModelEndpoint." requires-python = ">=3.11,<3.14" license = "Apache-2.0" dependencies = [ - "crossplane-function-sdk-python>=0.13.0", + "crossplane-function-sdk-python>=0.14.0", "click>=8.1.0", "grpcio>=1.73.1", "crossplane-models", diff --git a/functions/compose-model-replica/pyproject.toml b/functions/compose-model-replica/pyproject.toml index b776af114..8e76080e5 100644 --- a/functions/compose-model-replica/pyproject.toml +++ b/functions/compose-model-replica/pyproject.toml @@ -9,7 +9,7 @@ description = "Deploy a model on a single InferenceCluster." requires-python = ">=3.11,<3.14" license = "Apache-2.0" dependencies = [ - "crossplane-function-sdk-python>=0.13.0", + "crossplane-function-sdk-python>=0.14.0", "click>=8.1.0", "grpcio>=1.73.1", "crossplane-models", diff --git a/functions/compose-model-service/pyproject.toml b/functions/compose-model-service/pyproject.toml index 233348d82..eb962e415 100644 --- a/functions/compose-model-service/pyproject.toml +++ b/functions/compose-model-service/pyproject.toml @@ -9,7 +9,7 @@ description = "Compose a Gateway API HTTPRoute from a ModelService." requires-python = ">=3.11,<3.14" license = "Apache-2.0" dependencies = [ - "crossplane-function-sdk-python>=0.13.0", + "crossplane-function-sdk-python>=0.14.0", "click>=8.1.0", "grpcio>=1.73.1", "crossplane-models", diff --git a/functions/compose-serving-stack/pyproject.toml b/functions/compose-serving-stack/pyproject.toml index 45a43f531..a0b553e80 100644 --- a/functions/compose-serving-stack/pyproject.toml +++ b/functions/compose-serving-stack/pyproject.toml @@ -9,7 +9,7 @@ description = "Install the serving stack and supporting components on a remote c requires-python = ">=3.11,<3.14" license = "Apache-2.0" dependencies = [ - "crossplane-function-sdk-python>=0.13.0", + "crossplane-function-sdk-python>=0.14.0", "click>=8.1.0", "grpcio>=1.73.1", "crossplane-models", diff --git a/pyproject.toml b/pyproject.toml index db599a2db..ee7453d44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ members = ["functions/*", "schemas/python"] [dependency-groups] dev = [ - "crossplane-function-sdk-python>=0.13.0", + "crossplane-function-sdk-python>=0.14.0", ] [tool.ruff] diff --git a/schemas/.lock.json b/schemas/.lock.json index 1ddc95812..73b58f6e7 100644 --- a/schemas/.lock.json +++ b/schemas/.lock.json @@ -1 +1 @@ -{"packages":{"fs://apis":"319f7f96ce94d2299fc4538b432e579553e27290ceaaff8e226f1e51c8624665","git://https://github.com/crossplane/crossplane/cluster/crds":"32300d8ac6b01bb66702187eea8dee208511c75b","xpkg://xpkg.upbound.io/upbound/provider-aws-ec2:v2.5.0":"sha256:956e2688224c7b42fca94864991478b4ecfec9c9cf52f1cdc42300c1110cece8","xpkg://xpkg.upbound.io/upbound/provider-aws-eks:v2.5.0":"sha256:c51761a15be10295b2c95581448b180889653c20296ebac646624a04f3bf634e","xpkg://xpkg.upbound.io/upbound/provider-aws-iam:v2.5.0":"sha256:854fa2fe081872032cd528d29be58c47e7468744eba7da24b1214aa09610f836","xpkg://xpkg.upbound.io/upbound/provider-gcp-cloudplatform:v2.5.0":"sha256:c84812c0e07576ae1c14103a6f1e69cd1a77b341c5b7939e29c269dd8dc30e03","xpkg://xpkg.upbound.io/upbound/provider-gcp-compute:v2.5.0":"sha256:324c89628bad85f27202dd4322caf969e7d354c9d9895695984301533c939799","xpkg://xpkg.upbound.io/upbound/provider-gcp-container:v2.5.0":"sha256:6e3bb9a041cc1c06b2940904b9c865fe0ea0dd7229e7017dd768e7a55eb18669","xpkg://xpkg.upbound.io/upbound/provider-helm:v1.2.3":"sha256:d81cbe87c15c8555c8388bbb372387084ce8fefdf9a08d0a540a6b4d228a0049","xpkg://xpkg.upbound.io/upbound/provider-helm:v1.2.4":"sha256:e5896e93156845d4f3005abbffa5f1d9468b79944be54203c675f8b61ea54e19","xpkg://xpkg.upbound.io/upbound/provider-kubernetes:v1.2.4":"sha256:b32140022ee86530424da210ed8544f561592a350dc3467365719ffde8c124e1","xpkg://xpkg.upbound.io/upbound/provider-kubernetes:v1.2.5":"sha256:8c9788629608066abc5ba8ae7039214aeaa89619d810a0bfc337102c4f4a897d"}} \ No newline at end of file +{"packages":{"fs://apis":"8084d3526a478e60bf6f8b02eafb0f043198211008b15a5e6ffbbdb3083c9f30","git://https://github.com/crossplane/crossplane/cluster/crds":"90d8b72ad8b829f0bcd7d7d5a98eaa0d579f244a","xpkg://xpkg.upbound.io/upbound/provider-aws-ec2:v2.5.0":"sha256:956e2688224c7b42fca94864991478b4ecfec9c9cf52f1cdc42300c1110cece8","xpkg://xpkg.upbound.io/upbound/provider-aws-eks:v2.5.0":"sha256:c51761a15be10295b2c95581448b180889653c20296ebac646624a04f3bf634e","xpkg://xpkg.upbound.io/upbound/provider-aws-iam:v2.5.0":"sha256:854fa2fe081872032cd528d29be58c47e7468744eba7da24b1214aa09610f836","xpkg://xpkg.upbound.io/upbound/provider-gcp-cloudplatform:v2.5.0":"sha256:c84812c0e07576ae1c14103a6f1e69cd1a77b341c5b7939e29c269dd8dc30e03","xpkg://xpkg.upbound.io/upbound/provider-gcp-compute:v2.5.0":"sha256:324c89628bad85f27202dd4322caf969e7d354c9d9895695984301533c939799","xpkg://xpkg.upbound.io/upbound/provider-gcp-container:v2.5.0":"sha256:6e3bb9a041cc1c06b2940904b9c865fe0ea0dd7229e7017dd768e7a55eb18669","xpkg://xpkg.upbound.io/upbound/provider-helm:v1.2.3":"sha256:d81cbe87c15c8555c8388bbb372387084ce8fefdf9a08d0a540a6b4d228a0049","xpkg://xpkg.upbound.io/upbound/provider-helm:v1.2.4":"sha256:e5896e93156845d4f3005abbffa5f1d9468b79944be54203c675f8b61ea54e19","xpkg://xpkg.upbound.io/upbound/provider-helm:v1.2.5":"sha256:00756c2ac9af9d5addf32bc5ce61c7e99c222ff6286d284a419ee9fa48317f17","xpkg://xpkg.upbound.io/upbound/provider-kubernetes:v1.2.4":"sha256:b32140022ee86530424da210ed8544f561592a350dc3467365719ffde8c124e1","xpkg://xpkg.upbound.io/upbound/provider-kubernetes:v1.2.5":"sha256:8c9788629608066abc5ba8ae7039214aeaa89619d810a0bfc337102c4f4a897d"}} \ No newline at end of file diff --git a/schemas/python/models/ai/modelplane/modelcache/v1alpha1.py b/schemas/python/models/ai/modelplane/modelcache/v1alpha1.py index 53e8f6b63..017e51d01 100644 --- a/schemas/python/models/ai/modelplane/modelcache/v1alpha1.py +++ b/schemas/python/models/ai/modelplane/modelcache/v1alpha1.py @@ -3,7 +3,7 @@ from __future__ import annotations -from typing import Any, Literal +from typing import Literal from pydantic import AwareDatetime, BaseModel, conint, constr @@ -11,7 +11,7 @@ class ClusterSelector(BaseModel): - matchLabels: dict[str, Any] | None = None + matchLabels: dict[str, str] | None = None class CompositionRef(BaseModel): diff --git a/schemas/python/models/ai/modelplane/modeldeployment/v1alpha1.py b/schemas/python/models/ai/modelplane/modeldeployment/v1alpha1.py index ef1747833..08d5b2fe4 100644 --- a/schemas/python/models/ai/modelplane/modeldeployment/v1alpha1.py +++ b/schemas/python/models/ai/modelplane/modeldeployment/v1alpha1.py @@ -3,7 +3,7 @@ from __future__ import annotations -from typing import Any, Literal +from typing import Literal from pydantic import AwareDatetime, BaseModel, Field, conint, constr @@ -11,7 +11,7 @@ class ClusterSelector(BaseModel): - matchLabels: dict[str, Any] | None = None + matchLabels: dict[str, str] | None = None class CompositionRef(BaseModel): diff --git a/schemas/python/models/ai/modelplane/modelservice/v1alpha1.py b/schemas/python/models/ai/modelplane/modelservice/v1alpha1.py index d53ef646a..8a0d1ad8a 100644 --- a/schemas/python/models/ai/modelplane/modelservice/v1alpha1.py +++ b/schemas/python/models/ai/modelplane/modelservice/v1alpha1.py @@ -3,7 +3,7 @@ from __future__ import annotations -from typing import Any, Literal +from typing import Literal from pydantic import AwareDatetime, BaseModel, Field @@ -42,7 +42,7 @@ class Crossplane(BaseModel): class Selector(BaseModel): - matchLabels: dict[str, Any] + matchLabels: dict[str, str] class Endpoint(BaseModel): diff --git a/uv.lock b/uv.lock index 74624d0d8..f7ea5d016 100644 --- a/uv.lock +++ b/uv.lock @@ -83,7 +83,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.1.0" }, - { name = "crossplane-function-sdk-python", specifier = ">=0.13.0" }, + { name = "crossplane-function-sdk-python", specifier = ">=0.14.0" }, { name = "crossplane-models", editable = "schemas/python" }, { name = "grpcio", specifier = ">=1.73.1" }, ] @@ -102,7 +102,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.1.0" }, - { name = "crossplane-function-sdk-python", specifier = ">=0.13.0" }, + { name = "crossplane-function-sdk-python", specifier = ">=0.14.0" }, { name = "crossplane-models", editable = "schemas/python" }, { name = "grpcio", specifier = ">=1.73.1" }, ] @@ -121,7 +121,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.1.0" }, - { name = "crossplane-function-sdk-python", specifier = ">=0.13.0" }, + { name = "crossplane-function-sdk-python", specifier = ">=0.14.0" }, { name = "crossplane-models", editable = "schemas/python" }, { name = "grpcio", specifier = ">=1.73.1" }, ] @@ -140,7 +140,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.1.0" }, - { name = "crossplane-function-sdk-python", specifier = ">=0.13.0" }, + { name = "crossplane-function-sdk-python", specifier = ">=0.14.0" }, { name = "crossplane-models", editable = "schemas/python" }, { name = "grpcio", specifier = ">=1.73.1" }, ] @@ -160,7 +160,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.1.0" }, - { name = "crossplane-function-sdk-python", specifier = ">=0.13.0" }, + { name = "crossplane-function-sdk-python", specifier = ">=0.14.0" }, { name = "crossplane-models", editable = "schemas/python" }, { name = "grpcio", specifier = ">=1.73.1" }, { name = "pyyaml", specifier = ">=6.0" }, @@ -180,7 +180,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.1.0" }, - { name = "crossplane-function-sdk-python", specifier = ">=0.12.0" }, + { name = "crossplane-function-sdk-python", specifier = ">=0.14.0" }, { name = "crossplane-models", editable = "schemas/python" }, { name = "grpcio", specifier = ">=1.73.1" }, ] @@ -201,7 +201,7 @@ dependencies = [ requires-dist = [ { name = "cel-python", specifier = ">=0.3.0" }, { name = "click", specifier = ">=8.1.0" }, - { name = "crossplane-function-sdk-python", specifier = ">=0.13.0" }, + { name = "crossplane-function-sdk-python", specifier = ">=0.14.0" }, { name = "crossplane-models", editable = "schemas/python" }, { name = "grpcio", specifier = ">=1.73.1" }, ] @@ -220,7 +220,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.1.0" }, - { name = "crossplane-function-sdk-python", specifier = ">=0.13.0" }, + { name = "crossplane-function-sdk-python", specifier = ">=0.14.0" }, { name = "crossplane-models", editable = "schemas/python" }, { name = "grpcio", specifier = ">=1.73.1" }, ] @@ -239,7 +239,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.1.0" }, - { name = "crossplane-function-sdk-python", specifier = ">=0.13.0" }, + { name = "crossplane-function-sdk-python", specifier = ">=0.14.0" }, { name = "crossplane-models", editable = "schemas/python" }, { name = "grpcio", specifier = ">=1.73.1" }, ] @@ -258,7 +258,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.1.0" }, - { name = "crossplane-function-sdk-python", specifier = ">=0.13.0" }, + { name = "crossplane-function-sdk-python", specifier = ">=0.14.0" }, { name = "crossplane-models", editable = "schemas/python" }, { name = "grpcio", specifier = ">=1.73.1" }, ] @@ -278,7 +278,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.1.0" }, - { name = "crossplane-function-sdk-python", specifier = ">=0.13.0" }, + { name = "crossplane-function-sdk-python", specifier = ">=0.14.0" }, { name = "crossplane-models", editable = "schemas/python" }, { name = "grpcio", specifier = ">=1.73.1" }, { name = "pyyaml", specifier = ">=6.0" }, @@ -286,7 +286,7 @@ requires-dist = [ [[package]] name = "crossplane-function-sdk-python" -version = "0.13.0" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "grpcio" }, @@ -295,9 +295,9 @@ dependencies = [ { name = "pydantic" }, { name = "structlog" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/6d/2e30bba4bc941a15642ec37317e5b45f80d3a701344994d965b8dcd71bd6/crossplane_function_sdk_python-0.13.0.tar.gz", hash = "sha256:815007769ddeb3e2ea4d2b47f5feeb7b15a4692f052d62151e80ce30138eda2b", size = 60018, upload-time = "2026-06-08T20:46:01.76Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/2c/a5758e9233600b9cb76622f2eeb2dea097629cd82007631c5e9d67ee70e1/crossplane_function_sdk_python-0.14.0.tar.gz", hash = "sha256:bb54f37e743f9a980c1f1b05026e921ebaa293abdd054a045e8e34b926d99607", size = 60076, upload-time = "2026-06-10T23:30:05.259Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/9e/4cfc6a3d45f53a5b43a777b7363a0e83f87e96a03e95afc9169512f42cdb/crossplane_function_sdk_python-0.13.0-py3-none-any.whl", hash = "sha256:89ec8d5e5a66bc0ce4551bafe96546329fcc841e3c04706e5ed5e22a37bf4aad", size = 42972, upload-time = "2026-06-08T20:46:00.76Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/358b0000b7ab412c85e42b192fafbd1d877d5599702fbd808fe2ae9df530/crossplane_function_sdk_python-0.14.0-py3-none-any.whl", hash = "sha256:42323b086c4e8b8fa7d3ce032b5744c29db3cdcdaeb4681997dbf188639e8552", size = 43017, upload-time = "2026-06-10T23:30:04.34Z" }, ] [[package]] @@ -431,7 +431,7 @@ dev = [ [package.metadata] [package.metadata.requires-dev] -dev = [{ name = "crossplane-function-sdk-python", specifier = ">=0.13.0" }] +dev = [{ name = "crossplane-function-sdk-python", specifier = ">=0.14.0" }] [[package]] name = "pendulum"