Skip to content

feat(platform): package address-controller and its MetalLB driver - #3382

Draft
Timofei Larkin (lllamnyp) wants to merge 5 commits into
mainfrom
feat/address-controller-packages
Draft

Timofei Larkin (lllamnyp) wants to merge 5 commits into
mainfrom
feat/address-controller-packages

Conversation

@lllamnyp

@lllamnyp Timofei Larkin (lllamnyp) commented Jul 20, 2026 •

Copy link
Copy Markdown
Member

What this PR does

Adds IP addresses as a first-class resource (cozystack/community#35) as two optional system packages, off by default and enabled through bundles.enabledPackages:

  • cozystack.address-controller — the class-agnostic core (cozystack/address-controller): IPAddressClass / IPAddress / IPAddressClaim in local.sdn.cozystack.io, the PVC/PV-binding analog. It owns claim–address binding, status and reclaim, and never allocates anything itself.
  • cozystack.metallb-iad — the per-class driver for addresses announced by MetalLB (cozystack/metallb-iad). It renders one IPAddressPool (autoAssign: false) and L2Advertisement per served class, holds reservations as placeholder Services in its own namespace, and attaches an address to a tenant Service through the local.sdn.cozystack.io/ip-address-claim annotation.

Packaging

Both are ordinary wrapper packages under packages/system/, sourced from the cozystack-packages artifact like every other package. The upstream chart is vendored verbatim under charts/ by make update at a pinned commit of its main, since neither project publishes a chart artifact and the cozystack org has not cut a release of either yet. Each controller image is pinned by digest to the main-<sha> build of that same commit, in the wrapper's values.yaml, where hack/lib/image-refs.sh enumerates it. The images live under ghcr.io/cozystack/<name>, outside the ghcr.io/cozystack/cozystack/ repositories the release tooling owns, so promotion and the nightly mirror pass over them. The core installs its v1alpha1 CRDs with upgradeCRDs: CreateReplace.

What the wrappers add

  • Tenant access. The upstream chart is Cozystack-agnostic and grants only the controller itself. cozy:addresses, cozy:addresses:view and cozy:addresses:admin aggregate into cozy:tenant, cozy:tenant:view and cozy:tenant:admin respectively, so a tenant can manage IPAddressClaims in its own namespace, and the grant exists only while the package is installed. The cluster-scoped IPAddress and IPAddressClass are not granted: cozy:tenant* reaches a tenant through a per-namespace RoleBinding, so a rule for either would never be consulted. A tenant reads its address from the claim's status.addresses[].
  • An admission fence on the MetalLB annotations. cozystack-metallb-address-policy stops anyone except the driver's ServiceAccount from introducing or changing metallb.io/loadBalancerIPs or metallb.io/address-pool on a Service in a tenant-* namespace. Without it a tenant could steer MetalLB at an address reserved for someone else; the driver would flag the address Conflict, but only after the fact. The policy compares against oldObject rather than forbidding the annotations outright, because the driver writes the pin onto the tenant's own Service and the tenant must still be able to edit the rest of that Service.
  • The driver's MetalLB namespace, cozy-metallb instead of the upstream default metallb-system.

Variant scoping

cozystack.metallb-iad depends on cozystack.metallb, which is absent on isp-hosted, on a slim variant that did not opt into it, and wherever disabledPackages names it. The driver is emitted from the common-packages helper only when MetalLB's own include rendered a Package, so it cannot be enabled where it would sit in DependenciesNotReady forever, and it follows MetalLB automatically if MetalLB's rules change. The core carries no MetalLB dependency and remains available everywhere.

Validation

helm unittest covers the aggregation labels and rules on the tenant roles, the policy's annotation comparison, driver exemption, tenant scoping and capability gate, and the driver's per-variant emission. hack/select-install.sh --validate passes. Both packages run on a live Cozystack integration cluster, where the claim → reservation → Service attachment → theft refusal → Retain/re-adoption → teardown path has been exercised end to end.

Screenshots

Not a UI change.

Downstream repositories

The website is the one repository this change reaches: two new platform components need cards in guides/platform-stack/_index.md and operations/configuration/licenses.md. The follow-up is not open yet.

Release note

feat(platform): add IP addresses as a first-class resource as two optional system packages, `cozystack.address-controller` (IPAddressClass / IPAddress / IPAddressClaim) and its MetalLB driver `cozystack.metallb-iad`. Tenants can reserve an address with an IPAddressClaim and attach it to a LoadBalancer Service with the `local.sdn.cozystack.io/ip-address-claim` annotation. An admission policy stops tenants from setting MetalLB's pool or pin annotations directly. The driver is only emitted where MetalLB itself is, so it is not offered on isp-hosted, on a slim variant without MetalLB, or when MetalLB is disabled.

@github-actions github-actions Bot added size/M This PR changes 30-99 lines, ignoring generated files area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) kind/feature Categorizes issue or PR as related to a new feature labels Jul 20, 2026
@github-actions

Copy link
Copy Markdown

This PR has had no activity for 60 days and was marked lifecycle/stale.
It will be closed in 14 days unless commented or labelled lifecycle/frozen.

@github-actions github-actions Bot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale label Sep 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timofei Larkin (@lllamnyp) NOT LGTM, mostly because of where the code and images come from.

  1. Both PackageSources pull from github.com/lllamnyp/* on branch: main without spec.verify (sources/address-controller.yaml:13-14, sources/metallb-iad.yaml:13-14). Any push to those branches reaches clusters within minutes, with upgradeCRDs: CreateReplace, and metallb-iad has cluster-wide write on Services. Please pin a tag or a commit, or vendor the charts.
  2. The images are ghcr.io/lllamnyp/{address-controller,metallb-iad}:main with no digest and IfNotPresent. docs/agents/image-refs.md requires tag plus digest, and hack/lib/image-refs.sh does not see these refs, so promote, retag and mirror skip them. A release tag then does not say what it installs, and different nodes can run different builds.
  3. templates/sources.yaml renders every file under sources/, so the GitRepository objects exist on every cluster whether the package is enabled or not, and air-gapped clusters keep polling github.com.
  4. The commit carries Assisted-By: Claude <[email protected]> and the body has the "Generated with Claude Code" line. contributing.md accepts only Assisted-by: LLM. The PR template is not filled in either: no release note and no downstream checklist, and a new platform component is a website trigger.

There is also a conflict with main in bundles/system.yaml, where main added talos-log-collector; both sides should stay. helm unittest packages/core/platform passes, 88/88.

@lllamnyp
Timofei Larkin (lllamnyp) force-pushed the feat/address-controller-packages branch from aa66010 to 3ba44b1 Compare October 2, 2026 07:29
@lllamnyp Timofei Larkin (lllamnyp) changed the title feat(platform): address-controller + MetalLB IAD as git-sourced optional packages feat(platform): package address-controller and its MetalLB driver Oct 2, 2026
@lllamnyp

Copy link
Copy Markdown
Member Author

Aleksei Sviridkin (@lexfrei) thanks, all four points held. The branch is now rebuilt around them.

  1. Both charts are vendored now: packages/system/{address-controller,metallb-iad} are ordinary wrapper packages with the upstream chart under charts/, fetched by make update from the v0.1.0 release tags. The PackageSources point at cozystack-packages like everything else, so nothing reaches a cluster except what is in this tree.
  2. Both images are pinned to their 0.1.0 release builds as tag plus digest in the wrappers' own values.yaml, where collect_image_refs picks them up. They are third-party refs, so retag and mirror correctly pass over them. With a digest, IfNotPresent can no longer run different builds on different nodes.
  3. The GitRepository objects are gone entirely, so nothing polls github.com.
  4. The history is rewritten with Assisted-by: LLM only. The body now follows the template, with a release note and the downstream checklist. Both upstream repos now carry an Apache-2.0 LICENSE, which the website's licenses page links; that follow-up is noted in the body.

The bundles/system.yaml conflict with talos-log-collector is resolved with both kept, rebased on current main.

Two additions since the version you reviewed:

  • The wrapper grants tenants IPAddressClaim in their own namespace by aggregating into the cozy:tenant* roles.
  • It ships a VAP that stops tenants from introducing or changing metallb.io/loadBalancerIPs or metallb.io/address-pool directly.

The driver is also no longer offered on isp-hosted, where MetalLB does not run.

@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size/L This PR changes 100-499 lines, ignoring generated files and removed size/M This PR changes 30-99 lines, ignoring generated files labels Oct 2, 2026
Two optional system packages bring in IP addresses as a first-class resource
(cozystack/community#35): cozystack.address-controller, the class-agnostic
core (IPAddressClass / IPAddress / IPAddressClaim, local.sdn.cozystack.io),
and cozystack.metallb-iad, the driver implementing the contract for addresses
announced by MetalLB.

Both follow the umbrella shape: the upstream chart is vendored under charts/
at a pinned commit, and what belongs to Cozystack rather than to a generic
Kubernetes controller lives in the wrapper. Upstream cuts no tags and
publishes no chart artifact yet, so each `update` target clones at a SHA;
that becomes a tag once the projects start releasing.

The wrapper carries the tenant grant. The upstream chart is Cozystack-
agnostic and grants only the controller, so nothing there lets a tenant use
the API it serves; cozy:addresses, cozy:addresses:view and cozy:addresses:admin
aggregate into the platform's base tenant roles, which resolve by label, so
the grant is present exactly while the package is. Only IPAddressClaim is
granted: the other two kinds are cluster-scoped and cozy:tenant* reaches a
tenant through a per-namespace RoleBinding, so a rule for either could never
be consulted.

The wrapper also pins both controller images by digest, which the charts'
own floating :main defaults would otherwise leave free to move underneath a
cluster, and points the driver at cozy-metallb instead of the upstream
default metallb-system.

The driver's PackageSource declares dependsOn on the core, whose CRDs it
needs, and on cozystack.metallb. The core installs with upgradeCRDs:
CreateReplace since its CRDs are v1alpha1 and still evolving.

Assisted-by: LLM
Signed-off-by: Timofei Larkin <[email protected]>
cozystack.metallb-iad hard-depends on cozystack.metallb, and a driver
Package rendered without MetalLB beside it never leaves
DependenciesNotReady — the wedge the velero and bucket-application
comments in system.yaml already record for default packages, reachable
here through an opt-in one.

MetalLB is absent in three ways: on isp-hosted, which never calls the
common-packages helper; on a slim variant that did not opt into it; and
wherever disabledPackages names it. Rather than restate those rules for
the driver, the driver is gated on MetalLB's own include having rendered
a Package, so the two cannot drift when the rules for MetalLB change.
The class-agnostic core stays in the variant-agnostic body: it carries
no MetalLB dependency and is usable with another driver.

Assisted-by: LLM
Signed-off-by: Timofei Larkin <[email protected]>
A reservation is only as good as the annotations that express it. The
driver parks an address on a placeholder Service and hands it over by
pinning the workload's Service with metallb.io/loadBalancerIPs — ordinary
MetalLB annotations that any writer of a Service can set, so a tenant able
to set them can steer MetalLB at an address reserved for someone else. The
driver notices and marks the address Conflict, but only after the fact, and
during the handoff gap the address is briefly unheld in MetalLB's books
with admission the only thing in front of it.

The driver's design leaves this policy to the installation rather than the
controller, since who is allowed to pin is a property of the deployment;
this package is that installation, so the policy ships beside the chart it
protects and is withdrawn with it.

Presence of an annotation is deliberately not what is forbidden: the driver
writes the pin onto the tenant's own Service, and the tenant must stay able
to edit the rest of it. The comparison is against oldObject, so an
unchanged value always passes, while CREATE sees a null oldObject and reads
as absent. spec.loadBalancerIP needs no clause — MetalLB stopped honouring
it in v0.14 and this tree vendors v0.16.1.

Assisted-by: LLM
Signed-off-by: Timofei Larkin <[email protected]>
Both upstreams now cut releases, so the vendored charts move from bare
commits to the v0.1.0 tags and the controller images from the moving
:main builds to the 0.1.0 release builds. The image tag carries no
leading `v`: upstream's release job tags images through the
metadata-action semver pattern, which drops it, so a `:v0.1.0` image
reference would name a tag that does not exist.

Re-vendoring from the tags changes only the chart versions and drops
metallb-iad's values-cozystack.yaml, which upstream removed because this
wrapper already supplies the MetalLB namespace.

Assisted-by: LLM
Signed-off-by: Timofei Larkin <[email protected]>
@lllamnyp
Timofei Larkin (lllamnyp) force-pushed the feat/address-controller-packages branch from 3ba44b1 to 5279c46 Compare October 2, 2026 07:31
@github-actions github-actions Bot added size/XL This PR changes 500-999 lines, ignoring generated files and removed size/L This PR changes 100-499 lines, ignoring generated files lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale labels Oct 2, 2026
Both projects moved from lllamnyp/* into the cozystack org, which
publishes their images under ghcr.io/cozystack/. The org has so far
built only main, so the charts are vendored from the main commits those
builds came from and the images are pinned to the matching main-<sha>
tags by digest, keeping chart and controller from one source. The tag
in each ref names that commit, unlike the floating :main pointing at the
same digest today.

Re-vendoring changes only the repository links and the charts' own
default image, which the wrappers override.

Assisted-by: LLM
Signed-off-by: Timofei Larkin <[email protected]>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timofei Larkin (@lllamnyp) NOT LGTM, one thing left: a generated test fixture is stale, so Unit & controller tests is red and the required E2E Tests never ran.

The points from my last review are fixed at 1ae06175. The charts are vendored from cozystack/* at pinned commits and come from the cozystack-packages artifact, so no GitRepository is left and nothing polls github.com. The image digests match ghcr: a154074e... is address-controller:main-ee38d0a and 143bd582... is metallb-iad:main-7d0017a, the same commits the Makefiles pin, and hack/lib/image-refs.sh sees both refs. Trailers and the PR template are fine now, and a trial merge with current main is clean.

Blocker

The fixture packages/core/platform/tests/fixtures/selective-install-postgres-values.yaml was not regenerated after the two new PackageSources. hack/select-install_test.bats "generated Helm values fixture matches the current disable list" fails on this head (the Unit & controller tests run of 2026-10-07T11:28Z, attempt 1), and I get the same failure on a trial merge with main. hack/select-install.sh --disabled postgres now prints cozystack.address-controller and cozystack.metallb-iad, and the fixture has neither. Since that job is red, E2E (in-tree) was skipped. Regenerating the fixture from select-install.sh should fix it.

Non-blocking

  1. Please fold the last two commits into 2b9d011a. Each was true when written, but together they record three stops: a SHA in lllamnyp/*, then v0.1.0 in lllamnyp/*, then main-<sha> in cozystack/*. The repo merges with merge commits, so all three land in main. 5279c46b pins ghcr.io/lllamnyp/address-controller:0.1.0 and the next commit replaces it, and the body of 2b9d011a still says the update target "becomes a tag once the projects start releasing".
  2. The PR body says the cozystack org "has not cut a release of either yet". Both repos have a v0.1.0 tag, the pinned commits are a few commits ahead of it, and ghcr has no 0.1.0 image under ghcr.io/cozystack/. "Has not built a release image yet" would be accurate.
  3. Require API owner review for sizeable API changes is red because of the new API group. You are one of the two owners it lists, so it needs kvaps.

On the trial merge helm unittest passes for platform (268/268), address-controller (3/3) and metallb-iad (4/4), and hack/select-install.sh --validate passes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) kind/feature Categorizes issue or PR as related to a new feature size/XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants