Repository navigation
Description
Summary
createOrUpdateHelmRelease (package_reconciler.go L458-L506) looks HelmRelease up by (namespace, name). If one exists, it replaces spec, labels, annotations and ownerReferences with values computed for the current Package (L501-L504) and calls Update. It never checks who owns the existing object.
So two Packages that resolve to same (install.namespace, install.releaseName) fight over one release, and the later reconcile wins. The function comes from the first version of Package reconciler (f28d639ae, 2025-12-24), so it predates marketplace.
Impact
Before marketplace a Package came from cluster admin. Since #4308 connecting a tapped repository creates registration Package for default variant, so third-party content reaches this path without admin writing a manifest. Repository that publishes component with install: {namespace: cozy-cert-manager, releaseName: cert-manager} points at the platform's own release and replaces its spec and ownerReferences, and disconnecting it then deletes registration Package, with garbage collection following the replaced ownerReference.
Clash doesn't need hostile publisher. Platform ships release cozy-system/postgres-rd (postgres-application.yaml L26-L30), and cozypkg init postgres scaffolds component postgres-rd with install.namespace: cozy-system (init.go L70-L85). Release name defaults to component name (package_reconciler.go L302-L305), so third-party postgres app built from the scaffold targets the platform release by default. PackageSource collision check compares only PackageSource names, it doesn't catch this.
Same function is in v1.6.4 (package_reconciler.go L386-L432), so this is not marketplace only. Any Package goes through it, including one made with cozypkg add, and the fix needs a backport to release-1.6.
Not run on a cluster. What I read in code is the missing ownership check and the ownerReference replacement. What I inferred is the fight over the release and the garbage collection outcome through a tap.
#4361 and #4424 fixed several ApplicationDefinitions claiming one kind (updateHelmReleasesForAppDef), that is a different path and this function is untouched. Operator already refuses to apply over PackageSource it does not own (#4001, main.go L801-L812), and the review of #3940 put it as "the platform should never force-apply over an object it does not own". HelmRelease is second place that needs it.
Suggested fix
- Before the update compare controller ownerReference of existing HelmRelease with current Package (
metav1.IsControlledBy). On mismatch write nothing, set condition on Package and emit event naming current owner. - HelmRelease with no controller owner (made by hand or by installer) needs a decision. Upgrades of existing clusters must keep working, so safe default is to refuse unless release already carries this Package's labels. Up to whoever implements it.
- For planned moves of release between Packages require annotation on HelmRelease that names the Package allowed to take it.
Acceptance
- Package A owns a HelmRelease, Package B resolves to same name. After B reconcile release is unchanged and B reports condition naming A.
- Reconcile of A still updates the release.
- Cluster upgraded from 1.6 reconciles all platform releases as before.
- Unit tests for the three cases.
Activity