From eb39a401baa8c8f7cac2a6fbbbee966643b5cc93 Mon Sep 17 00:00:00 2001 From: Giovanni Ferri Date: Wed, 8 Jul 2026 22:34:54 +0100 Subject: [PATCH 1/2] fix(charts): resync drifted CRDs from base + add make sync-chart-crds The Helm chart CRDs under charts/imp-crds/templates had drifted from config/crd/bases: impnetworks, impvmrunnerpools, impvmtemplates, and impwarmpools were missing schema added since (expireAfter, scaling modes, groupCIDRs, VTEP list-map-key, etc.), so a chart install shipped stale CRDs that reject valid spec fields. Resync all ten from base and add a make sync-chart-crds target (base + helm.sh/resource-policy: keep) so the chart CRDs regenerate mechanically and cannot silently drift again. make manifests only writes the base copies; there was no automation keeping the chart in step. --- Makefile | 8 ++ charts/imp-crds/templates/impnetworks.yaml | 38 +++++++- .../imp-crds/templates/impvmrunnerpools.yaml | 95 +++++++++++++++++-- charts/imp-crds/templates/impvmtemplates.yaml | 8 ++ charts/imp-crds/templates/impwarmpools.yaml | 8 ++ 5 files changed, 146 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index f8554a0..304643e 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,14 @@ help: ## Display this help. manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. "$(CONTROLLER_GEN)" rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases +.PHONY: sync-chart-crds +sync-chart-crds: manifests ## Regenerate Helm chart CRDs from config/crd/bases (adds helm.sh/resource-policy: keep). + @for base in config/crd/bases/imp.dev_*.yaml; do \ + plural=$$(basename $$base | sed 's/^imp.dev_//'); \ + awk '{print} /^ annotations:$$/ {print " \"helm.sh/resource-policy\": keep"}' $$base > charts/imp-crds/templates/$$plural; \ + done + @echo "Synced $$(ls config/crd/bases/imp.dev_*.yaml | wc -l | tr -d ' ') CRDs to charts/imp-crds/templates/" + .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. "$(CONTROLLER_GEN)" object:headerFile="hack/boilerplate.go.txt" paths="./..." diff --git a/charts/imp-crds/templates/impnetworks.yaml b/charts/imp-crds/templates/impnetworks.yaml index 3fb49ba..a6631f8 100644 --- a/charts/imp-crds/templates/impnetworks.yaml +++ b/charts/imp-crds/templates/impnetworks.yaml @@ -124,9 +124,16 @@ spec: description: Cilium configures Cilium IPAM. Required when Provider is "cilium". properties: + cidr: + description: |- + Cidr overrides the CIDR used when Imp auto-creates the CiliumPodIPPool. + Defaults to ImpNetwork.spec.subnet when unset. + pattern: ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ + type: string poolRef: - description: PoolRef is the name of the CiliumPodIPPool resource - to allocate from. + description: |- + PoolRef is the name of the CiliumPodIPPool resource to allocate from. + Imp creates and owns this pool automatically; it need not exist beforehand. type: string required: - poolRef @@ -232,6 +239,29 @@ spec: x-kubernetes-list-map-keys: - type x-kubernetes-list-type: map + groupCIDRs: + description: |- + GroupCIDRs contains the allocated subnet CIDR for each network group in spec.groups. + Populated and maintained by the controller; do not edit manually. + items: + description: GroupCIDR records the subnet CIDR allocated to a named + network group. + properties: + cidr: + description: CIDR is the subnet allocated for this group (e.g. + "10.44.0.0/28"). + type: string + name: + description: Name is the group name (matches spec.groups[].name). + type: string + required: + - cidr + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map vtepTable: description: |- VTEPTable contains VTEP entries for cross-node VXLAN FDB population. @@ -257,7 +287,9 @@ spec: - vmMAC type: object type: array - x-kubernetes-list-type: atomic + x-kubernetes-list-map-keys: + - vmIP + x-kubernetes-list-type: map type: object type: object served: true diff --git a/charts/imp-crds/templates/impvmrunnerpools.yaml b/charts/imp-crds/templates/impvmrunnerpools.yaml index eac6396..ef742fb 100644 --- a/charts/imp-crds/templates/impvmrunnerpools.yaml +++ b/charts/imp-crds/templates/impvmrunnerpools.yaml @@ -63,9 +63,18 @@ spec: description: ImpVMRunnerPoolSpec defines a pool of ephemeral CI runner VMs. properties: + expireAfter: + description: |- + ExpireAfter sets VM expiration for runner VMs created by this pool. + 0 or unset disables automatic expiration. Minimum enabled value is 60s. + type: string + x-kubernetes-validations: + - message: expireAfter must be 0 (disabled) or at least 60s + rule: duration(self) == duration('0s') || duration(self) >= duration('60s') jobDetection: - description: JobDetection configures how the operator discovers queued - jobs. + description: |- + JobDetection configures how the operator discovers queued jobs. + Deprecated: use spec.scaling.mode + spec.scaling.webhook/polling instead. properties: polling: description: Polling enables periodic API polling as a fallback. @@ -111,15 +120,17 @@ spec: properties: org: description: Org registers a runner for the entire organisation. + minLength: 1 type: string repo: description: Repo registers a runner for a single repository ("owner/repo"). + minLength: 1 type: string type: object x-kubernetes-validations: - message: set exactly one of org or repo - rule: (size(self.org) > 0) != (size(self.repo) > 0) + rule: has(self.org) != has(self.repo) serverURL: description: ServerURL is required for GitLab and Forgejo. Leave empty for github.com. @@ -144,23 +155,63 @@ spec: scaling: description: Scaling controls how many runner VMs are maintained. properties: + cooldownSeconds: + description: CooldownSeconds is the minimum wait before next scaling + reconcile cycle. + format: int32 + minimum: 10 + type: integer maxConcurrent: - default: 10 description: MaxConcurrent is the hard cap on simultaneous runner VMs. format: int32 - maximum: 100 minimum: 1 type: integer minIdle: - default: 0 description: |- MinIdle is the number of pre-registered idle runner VMs to keep available. - 0 means pure on-demand — no idle VMs sit waiting. + 0 means pure on-demand - no idle VMs sit waiting. format: int32 - maximum: 3 minimum: 0 type: integer + mode: + description: Mode selects which demand sources are used. + enum: + - webhook + - polling + - hybrid + type: string + polling: + description: Polling config for polling/hybrid mode. + properties: + enabled: + description: Enabled turns on polling-based job detection. + type: boolean + intervalSeconds: + default: 30 + description: IntervalSeconds is how often the operator polls + the platform API. + format: int32 + minimum: 10 + type: integer + type: object + scaleUpStep: + description: ScaleUpStep limits how many new VMs can be created + per reconcile. + format: int32 + minimum: 1 + type: integer + webhook: + description: Webhook config for webhook/hybrid mode. + properties: + enabled: + description: Enabled turns on webhook-based job detection. + type: boolean + secretRef: + description: SecretRef names a Secret containing the HMAC + webhook secret. + type: string + type: object type: object templateName: description: TemplateName references an ImpVMTemplate in the same @@ -170,6 +221,34 @@ spec: - platform - templateName type: object + x-kubernetes-validations: + - message: scaling is required for github-actions pools + rule: self.platform.type != 'github-actions' || has(self.scaling) + - message: scaling.mode is required for github-actions pools + rule: self.platform.type != 'github-actions' || size(self.scaling.mode) + > 0 + - message: github-actions scaling requires explicit minIdle, maxConcurrent, + scaleUpStep, and cooldownSeconds + rule: self.platform.type != 'github-actions' || (has(self.scaling.minIdle) + && has(self.scaling.maxConcurrent) && has(self.scaling.scaleUpStep) + && has(self.scaling.cooldownSeconds)) + - message: scaling.minIdle must be <= scaling.maxConcurrent + rule: self.platform.type != 'github-actions' || self.scaling.minIdle + <= self.scaling.maxConcurrent + - message: scaling.polling is required when mode=polling + rule: self.platform.type != 'github-actions' || self.scaling.mode != + 'polling' || has(self.scaling.polling) + - message: scaling.polling is required when mode=hybrid + rule: self.platform.type != 'github-actions' || self.scaling.mode != + 'hybrid' || has(self.scaling.polling) + - message: scaling.webhook.secretRef is required when mode=webhook + rule: self.platform.type != 'github-actions' || self.scaling.mode != + 'webhook' || (has(self.scaling.webhook) && size(self.scaling.webhook.secretRef) + > 0) + - message: scaling.webhook.secretRef is required when mode=hybrid + rule: self.platform.type != 'github-actions' || self.scaling.mode != + 'hybrid' || (has(self.scaling.webhook) && size(self.scaling.webhook.secretRef) + > 0) status: description: ImpVMRunnerPoolStatus reflects the observed pool state. properties: diff --git a/charts/imp-crds/templates/impvmtemplates.yaml b/charts/imp-crds/templates/impvmtemplates.yaml index f763c8d..0b755f5 100644 --- a/charts/imp-crds/templates/impvmtemplates.yaml +++ b/charts/imp-crds/templates/impvmtemplates.yaml @@ -70,6 +70,14 @@ spec: required: - name type: object + expireAfter: + description: |- + ExpireAfter sets default VM expiration for VMs created from this template. + 0 or unset disables automatic expiration. Minimum enabled value is 60s. + type: string + x-kubernetes-validations: + - message: expireAfter must be 0 (disabled) or at least 60s + rule: duration(self) == duration('0s') || duration(self) >= duration('60s') guestAgent: description: GuestAgent controls guest agent injection. Overrides defaults when set. diff --git a/charts/imp-crds/templates/impwarmpools.yaml b/charts/imp-crds/templates/impwarmpools.yaml index 019a981..5a7abe2 100644 --- a/charts/imp-crds/templates/impwarmpools.yaml +++ b/charts/imp-crds/templates/impwarmpools.yaml @@ -60,6 +60,14 @@ spec: description: ImpWarmPoolSpec defines a pool of pre-booted VMs ready for instant assignment. properties: + expireAfter: + description: |- + ExpireAfter sets VM expiration for members created by this pool. + 0 or unset disables automatic expiration. Minimum enabled value is 60s. + type: string + x-kubernetes-validations: + - message: expireAfter must be 0 (disabled) or at least 60s + rule: duration(self) == duration('0s') || duration(self) >= duration('60s') size: default: 2 description: Size is the number of pre-booted VMs to maintain in the From 5c28dd5974d79881d0a813f813f9eaf01985f63a Mon Sep 17 00:00:00 2001 From: Giovanni Ferri Date: Wed, 8 Jul 2026 22:47:16 +0100 Subject: [PATCH 2/2] test(e2e): update runner pool fixture to current scaling API The smoke suite installs the Helm chart CRDs, which were stale and lacked the scaling.mode + CEL validations now synced from base. The fixture used the deprecated scaling {minIdle, maxConcurrent} + jobDetection.webhook shape, which the corrected CRD rejects. Move it to scaling.mode=webhook with explicit minIdle/maxConcurrent/scaleUpStep/ cooldownSeconds + scaling.webhook.secretRef. Demand=2 capped at scaleUpStep=2 still yields the 2 VMs the test asserts. --- test/e2e/e2e_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 9c2e921..ca47cfb 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -166,11 +166,14 @@ spec: type: github-actions credentialsSecret: ignored-when-webhook-only scaling: + mode: webhook minIdle: 0 maxConcurrent: 5 - jobDetection: + scaleUpStep: 2 + cooldownSeconds: 10 webhook: enabled: true + secretRef: e2e-webhook-secret `, poolName, templateName) poolApply := exec.Command("kubectl", "apply", "-f", "-") poolApply.Stdin = strings.NewReader(poolManifest)