From ea00ad5822b8fc57e56b70bca5cc2423b986a358 Mon Sep 17 00:00:00 2001 From: "Patrick J. McNerthney" Date: Thu, 21 Aug 2025 10:57:51 -1000 Subject: [PATCH 1/4] Add PyPi build and publish --- Dockerfile | 2 +- README.md | 10 +++++++++- pyproject.toml | 4 +--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3326cb4..8138472 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ COPY dist/*.whl /root WORKDIR / RUN \ set -eux && \ - pip install --root-user-action ignore --no-build-isolation $(echo /root/*.whl)[packages,pip-install] && \ + pip install --root-user-action ignore --no-build-isolation /root/*.whl && \ rm -rf /root/*.whl /root/.cache && \ groupadd --gid 2000 pythonic && \ useradd --uid 2000 --gid pythonic --home-dir /opt/pythonic --create-home --shell /usr/sbin/nologin pythonic diff --git a/README.md b/README.md index 3668d8e..c6f9192 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,7 @@ Each resource in the list is the following RequiredResource class: ### Conditions -The `BaseComposite.conditions`, `Resource.conditions`, and `RequiredResource.conditions` fields +The `BaseCompsite.conditions`, `Resource.conditions`, and `RequiredResource.conditions` fields are maps of that entity's status conditions array, with the map key being the condition type. The fields are read only for `Resource.conditions` and `RequiredResource.conditions`. @@ -349,7 +349,11 @@ spec: apiVersion: pythonic.fn.fortra.com/v1alpha1 kind: Composite composite: | +<<<<<<< HEAD class GreetingComposite(BaseComposite): +======= + class Composite(BaseComposite): +>>>>>>> 185c4c0 (Add PyPi build and publish) def compose(self): self.status.greeting = f"Hello, {self.spec.who}!" ``` @@ -362,7 +366,11 @@ metadata: annotations: render.crossplane.io/runtime: Development spec: +<<<<<<< HEAD package: ghcr.io/fortra/function-pythonic:v0.0.10 +======= + package: ghcr.io/fortra/function-pythonic:v0.0.7 +>>>>>>> 185c4c0 (Add PyPi build and publish) ``` In one terminal session, run function-pythonic: ```shell diff --git a/pyproject.toml b/pyproject.toml index 9cbccaa..f76a7a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,9 +21,7 @@ dependencies = [ "pyyaml==6.0.2", ] -[project.optional-dependencies] -packages = ["kopf==1.38.0"] -pip-install = ["pip==25.2"] +dynamic = ["version"] [project.urls] Documentation = "https://github.com/fortra/function-pythonic#readme" From 338480360cd044c4fb07d8a306a9a0ea2505261e Mon Sep 17 00:00:00 2001 From: "Patrick J. McNerthney" Date: Sat, 23 Aug 2025 19:02:56 -1000 Subject: [PATCH 2/4] Make the --pip-install and --packages dependencies optional --- Dockerfile | 2 +- pyproject.toml | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8138472..3326cb4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ COPY dist/*.whl /root WORKDIR / RUN \ set -eux && \ - pip install --root-user-action ignore --no-build-isolation /root/*.whl && \ + pip install --root-user-action ignore --no-build-isolation $(echo /root/*.whl)[packages,pip-install] && \ rm -rf /root/*.whl /root/.cache && \ groupadd --gid 2000 pythonic && \ useradd --uid 2000 --gid pythonic --home-dir /opt/pythonic --create-home --shell /usr/sbin/nologin pythonic diff --git a/pyproject.toml b/pyproject.toml index f76a7a5..9cbccaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,9 @@ dependencies = [ "pyyaml==6.0.2", ] -dynamic = ["version"] +[project.optional-dependencies] +packages = ["kopf==1.38.0"] +pip-install = ["pip==25.2"] [project.urls] Documentation = "https://github.com/fortra/function-pythonic#readme" From ff417a7b4b38cb70dcd760e61b2c5c774d457eaf Mon Sep 17 00:00:00 2001 From: drew0ps Date: Sat, 23 Aug 2025 02:06:29 +0200 Subject: [PATCH 3/4] Initial commit for simple AKS cluster implementation --- examples/aks-cluster/README.md | 32 ++++++++++ .../cluster-function-pythonic.yaml | 63 +++++++++++++++++++ examples/aks-cluster/composition.yaml | 24 +++++++ examples/aks-cluster/definition.yaml | 43 +++++++++++++ examples/aks-cluster/functions.yaml | 10 +++ examples/aks-cluster/install.sh | 13 ++++ examples/aks-cluster/kubernetescluster.py | 29 +++++++++ examples/aks-cluster/kustomization.yaml | 20 ++++++ examples/aks-cluster/providers.yaml | 14 +++++ examples/aks-cluster/render.sh | 9 +++ examples/aks-cluster/resourcegroup.py | 14 +++++ examples/aks-cluster/xr.yaml | 15 +++++ 12 files changed, 286 insertions(+) create mode 100644 examples/aks-cluster/README.md create mode 100644 examples/aks-cluster/cluster-function-pythonic.yaml create mode 100644 examples/aks-cluster/composition.yaml create mode 100644 examples/aks-cluster/definition.yaml create mode 100644 examples/aks-cluster/functions.yaml create mode 100755 examples/aks-cluster/install.sh create mode 100644 examples/aks-cluster/kubernetescluster.py create mode 100644 examples/aks-cluster/kustomization.yaml create mode 100644 examples/aks-cluster/providers.yaml create mode 100755 examples/aks-cluster/render.sh create mode 100644 examples/aks-cluster/resourcegroup.py create mode 100644 examples/aks-cluster/xr.yaml diff --git a/examples/aks-cluster/README.md b/examples/aks-cluster/README.md new file mode 100644 index 0000000..efbb56b --- /dev/null +++ b/examples/aks-cluster/README.md @@ -0,0 +1,32 @@ +# AKS Cluster Example + +This example demonstrates how to provision an AKS cluster using Crossplane v1. + +## Prerequisites + +- Kubernetes cluster (v1.31+) +- Helm v3.x + +## Install Crossplane v1 via Helm + +1. **Add the Crossplane Helm repository:** + ```sh + helm repo add crossplane-stable https://charts.crossplane.io/stable + helm repo update + ``` + +2. **Install Crossplane v1:** + ```sh + helm install crossplane --namespace crossplane-system --create-namespace crossplane-stable/crossplane --version 1.x.x + ``` + Replace `1.x.x` with the desired Crossplane v1 release. + +3. **Verify installation:** + ```sh + kubectl get pods -n crossplane-system + ``` + +4. **Apply the manifests:** + ```sh + ./install.sh + ``` \ No newline at end of file diff --git a/examples/aks-cluster/cluster-function-pythonic.yaml b/examples/aks-cluster/cluster-function-pythonic.yaml new file mode 100644 index 0000000..ccd98af --- /dev/null +++ b/examples/aks-cluster/cluster-function-pythonic.yaml @@ -0,0 +1,63 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-pythonic +spec: + package: ghcr.io/fortra/function-pythonic:v0.0.8 + runtimeConfigRef: + name: function-pythonic +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: DeploymentRuntimeConfig +metadata: + name: function-pythonic +spec: + deploymentTemplate: + spec: + selector: {} + template: + spec: + containers: + - name: package-runtime + args: + - --debug + - --packages + - --packages-namespace=crossplane-system + serviceAccountName: function-pythonic + serviceAccountTemplate: + metadata: + name: function-pythonic +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: crossplane-system + name: function-pythonic +rules: +- apiGroups: + - '' + resources: + - configmaps + verbs: + - list + - watch + - patch +- apiGroups: + - '' + resources: + - events + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + namespace: crossplane-system + name: function-pythonic +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: function-pythonic +subjects: +- kind: ServiceAccount + name: function-pythonic diff --git a/examples/aks-cluster/composition.yaml b/examples/aks-cluster/composition.yaml new file mode 100644 index 0000000..8573204 --- /dev/null +++ b/examples/aks-cluster/composition.yaml @@ -0,0 +1,24 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: aks-cluster +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: AzureKubernetesCluster + mode: Pipeline + pipeline: + - step: create-resourcegroup + functionRef: + name: function-pythonic + input: + apiVersion: pythonic.fn.fortra.com/v1alpha1 + kind: Composite + composite: resourcegroup.ResourceGroupComposite + - step: create-aks-cluster + functionRef: + name: function-pythonic + input: + apiVersion: pythonic.fn.fortra.com/v1alpha1 + kind: Composite + composite: kubernetescluster.KubernetesClusterComposite diff --git a/examples/aks-cluster/definition.yaml b/examples/aks-cluster/definition.yaml new file mode 100644 index 0000000..380b6bf --- /dev/null +++ b/examples/aks-cluster/definition.yaml @@ -0,0 +1,43 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: CompositeResourceDefinition +metadata: + name: azurekubernetesclusters.example.crossplane.io +spec: + group: example.crossplane.io + names: + kind: AzureKubernetesCluster + plural: azurekubernetesclusters + claimNames: + kind: AzureKubernetesClusterClaim + plural: azurekubernetesclusterclaims + versions: + - name: v1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + defaultNodePool: + type: object + properties: + autoScaling: + type: object + properties: + enabled: + type: boolean + minCount: + type: integer + maxCount: + type: integer + nodeCount: + type: integer + vmSize: + type: string + resourceGroupName: + type: string + location: + type: string diff --git a/examples/aks-cluster/functions.yaml b/examples/aks-cluster/functions.yaml new file mode 100644 index 0000000..064538f --- /dev/null +++ b/examples/aks-cluster/functions.yaml @@ -0,0 +1,10 @@ +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: function-pythonic + annotations: + render.crossplane.io/runtime: Development +spec: + package: ghcr.io/fortra/function-pythonic:v0.0.8 + runtimeConfigRef: + name: function-pythonic diff --git a/examples/aks-cluster/install.sh b/examples/aks-cluster/install.sh new file mode 100755 index 0000000..47e2446 --- /dev/null +++ b/examples/aks-cluster/install.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# Install function-pythonic and azure providers +kubectl apply -f cluster-function-pythonic.yaml +kubectl apply -f providers.yaml + +# Install AKS Crossplane XRDs and Compositions +kubectl apply -f definition.yaml +# Wait for the generated CRDs to be created +sleep 5 + +# Create the AKS cluster +kubectl apply -k . diff --git a/examples/aks-cluster/kubernetescluster.py b/examples/aks-cluster/kubernetescluster.py new file mode 100644 index 0000000..66f5f9c --- /dev/null +++ b/examples/aks-cluster/kubernetescluster.py @@ -0,0 +1,29 @@ +from crossplane.pythonic import BaseComposite + +class KubernetesClusterComposite(BaseComposite): + def compose(self): + labels = {'example.crossplane.io/AzureKubernetesCluster': self.metadata.name} + self.logger.info(f"Composing AKS cluster {self.metadata.name}") + + aks = self.resources.KubernetesCluster( + 'containerservice.azure.upbound.io/v1beta2', 'KubernetesCluster' + ) + aks.metadata.name = self.metadata.name + aks.metadata.labels = labels + + aks.spec.forProvider.location = self.spec.location + aks.spec.forProvider.resourceGroupNameRef.name = self.spec.resourceGroupName + aks.spec.forProvider.identity.type = 'SystemAssigned' + + if self.spec.defaultNodePool.autoScaling.enabled: + aks.spec.forProvider.defaultNodePool.autoScalingEnabled = True + aks.spec.forProvider.defaultNodePool.minCount = self.spec.defaultNodePool.autoScaling.minCount + aks.spec.forProvider.defaultNodePool.maxCount = self.spec.defaultNodePool.autoScaling.maxCount + else: + aks.spec.forProvider.defaultNodePool.nodeCount = self.spec.nodeCount + + aks.spec.forProvider.defaultNodePool.name = 'systempool1' + aks.spec.forProvider.defaultNodePool.vmSize = self.spec.vmSize + aks.spec.forProvider.dnsPrefix = self.metadata.name + + self.status.aks = aks.status.endpoint diff --git a/examples/aks-cluster/kustomization.yaml b/examples/aks-cluster/kustomization.yaml new file mode 100644 index 0000000..7edc650 --- /dev/null +++ b/examples/aks-cluster/kustomization.yaml @@ -0,0 +1,20 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +generatorOptions: + disableNameSuffixHash: true + +configMapGenerator: + +- namespace: crossplane-system + name: pythonic-aks + options: + labels: + function-pythonic.package: '' + files: + - kubernetescluster.py + - resourcegroup.py + +resources: +- composition.yaml +- xr.yaml diff --git a/examples/aks-cluster/providers.yaml b/examples/aks-cluster/providers.yaml new file mode 100644 index 0000000..82106a3 --- /dev/null +++ b/examples/aks-cluster/providers.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: upbound-provider-family-azure +spec: + package: xpkg.upbound.io/upbound/provider-family-azure:v1 +--- +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: upbound-provider-azure-containerservice +spec: + package: xpkg.upbound.io/upbound/provider-azure-containerservice:v1 diff --git a/examples/aks-cluster/render.sh b/examples/aks-cluster/render.sh new file mode 100755 index 0000000..2a42755 --- /dev/null +++ b/examples/aks-cluster/render.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# In one terminal +# PYTHONPATH=. function-pythonic --insecure --debug + +# In another terminal: + +cd $(dirname $(realpath $0)) +exec crossplane render xr.yaml composition.yaml functions.yaml diff --git a/examples/aks-cluster/resourcegroup.py b/examples/aks-cluster/resourcegroup.py new file mode 100644 index 0000000..91664ae --- /dev/null +++ b/examples/aks-cluster/resourcegroup.py @@ -0,0 +1,14 @@ +from crossplane.pythonic import BaseComposite + +class ResourceGroupComposite(BaseComposite): + def compose(self): + labels = {'example.crossplane.io/AzureKubernetesCluster': self.metadata.name} + self.logger.info(f"Composing Azure ResourceGroup {self.spec.resourceGroupName}") + + rg = self.resources.ResourceGroup( + 'azure.upbound.io/v1beta1', 'ResourceGroup' + ) + rg.metadata.name = self.spec.resourceGroupName + rg.metadata.labels = labels + + rg.spec.forProvider.location = self.spec.location diff --git a/examples/aks-cluster/xr.yaml b/examples/aks-cluster/xr.yaml new file mode 100644 index 0000000..a567ddf --- /dev/null +++ b/examples/aks-cluster/xr.yaml @@ -0,0 +1,15 @@ +apiVersion: example.crossplane.io/v1 +kind: AzureKubernetesCluster +metadata: + namespace: my-aks-namespace + name: my-aks-cluster +spec: + defaultNodePool: + autoScaling: + enabled: False + minCount: 1 + maxCount: 3 + nodeCount: 1 + vmSize: Standard_B2ms + resourceGroupName: my-resource-group + location: westeurope From b1c1608741b8fa8eb9a1becb01cd952a6056d114 Mon Sep 17 00:00:00 2001 From: drew0ps Date: Tue, 26 Aug 2025 18:28:49 +0200 Subject: [PATCH 4/4] Rebase complete --- README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index c6f9192..3668d8e 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,7 @@ Each resource in the list is the following RequiredResource class: ### Conditions -The `BaseCompsite.conditions`, `Resource.conditions`, and `RequiredResource.conditions` fields +The `BaseComposite.conditions`, `Resource.conditions`, and `RequiredResource.conditions` fields are maps of that entity's status conditions array, with the map key being the condition type. The fields are read only for `Resource.conditions` and `RequiredResource.conditions`. @@ -349,11 +349,7 @@ spec: apiVersion: pythonic.fn.fortra.com/v1alpha1 kind: Composite composite: | -<<<<<<< HEAD class GreetingComposite(BaseComposite): -======= - class Composite(BaseComposite): ->>>>>>> 185c4c0 (Add PyPi build and publish) def compose(self): self.status.greeting = f"Hello, {self.spec.who}!" ``` @@ -366,11 +362,7 @@ metadata: annotations: render.crossplane.io/runtime: Development spec: -<<<<<<< HEAD package: ghcr.io/fortra/function-pythonic:v0.0.10 -======= - package: ghcr.io/fortra/function-pythonic:v0.0.7 ->>>>>>> 185c4c0 (Add PyPi build and publish) ``` In one terminal session, run function-pythonic: ```shell