mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-29 16:17:20 +09:00
Nothing enforced that spec.application.kind is unique, and each consumer that maps a kind back to a definition resolved a duplicate its own way: the chartRef reconciler let every definition rewrite every release of its kind, the lineage webhook and the CA projection reconciler took whichever came first in an unordered list, and cozystack-api registered one resource per definition. A second definition declaring a kind already in use could therefore repoint that kind's tenant releases at its own artifact. Add one ownership rule, the definition created first owns the kind with ties broken by name, and route every consumer through it. A definition that arrives later with a kind already in use no longer manages that kind's releases whatever it is named: the chartRef reconciler skips it with a Warning event and re-reconciles the next owner when the current one goes, cozystack-api registers the owner only and logs the rest, its restart hash moves when ownership moves, and the lineage and CA projection lookups resolve the owner. Assisted-by: LLM Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
65 lines
2.6 KiB
Go
65 lines
2.6 KiB
Go
package server
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
helmv2 "github.com/fluxcd/helm-controller/api/v2"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"github.com/cozystack/cozystack/api/v1alpha1"
|
|
)
|
|
|
|
// TestOwnedDefinitionsDropsKindDuplicate pins that cozystack-api registers one
|
|
// resource per kind: a definition declaring a kind an older definition already
|
|
// owns is left out rather than registered as a second resource for one GVK, and
|
|
// is reported so the operator can see why its kind is missing.
|
|
func TestOwnedDefinitionsDropsKindDuplicate(t *testing.T) {
|
|
base := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
mk := func(name, kind string, at time.Time) v1alpha1.ApplicationDefinition {
|
|
d := v1alpha1.ApplicationDefinition{ObjectMeta: metav1.ObjectMeta{Name: name, CreationTimestamp: metav1.NewTime(at)}}
|
|
d.Spec.Application.Kind = kind
|
|
return d
|
|
}
|
|
kept, skipped := ownedDefinitions([]v1alpha1.ApplicationDefinition{
|
|
mk("a-redis-shadow", "Redis", base.Add(time.Hour)),
|
|
mk("redis", "Redis", base),
|
|
mk("postgres", "Postgres", base),
|
|
mk("no-kind", "", base),
|
|
})
|
|
names := map[string]bool{}
|
|
for _, d := range kept {
|
|
names[d.Name] = true
|
|
}
|
|
// A definition with no kind claims nothing, so it is not anyone's duplicate.
|
|
if len(kept) != 3 || !names["redis"] || !names["postgres"] || !names["no-kind"] {
|
|
t.Fatalf("kept = %v, want redis, postgres and no-kind", names)
|
|
}
|
|
if len(skipped) != 1 || skipped[0] != `ApplicationDefinition "a-redis-shadow" declares kind Redis already owned by "redis"` {
|
|
t.Fatalf("skipped = %q", skipped)
|
|
}
|
|
}
|
|
|
|
// TestResourceConfigFromRegistersOwnerOnly pins that the resource config
|
|
// cozystack-api serves is built from the owners only: one resource per kind,
|
|
// carrying the owner's chart.
|
|
func TestResourceConfigFromRegistersOwnerOnly(t *testing.T) {
|
|
base := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
mk := func(name, artifact string, at time.Time) v1alpha1.ApplicationDefinition {
|
|
d := v1alpha1.ApplicationDefinition{ObjectMeta: metav1.ObjectMeta{Name: name, CreationTimestamp: metav1.NewTime(at)}}
|
|
d.Spec.Application.Kind = "Redis"
|
|
d.Spec.Release.ChartRef = &helmv2.CrossNamespaceSourceReference{Kind: "ExternalArtifact", Name: artifact, Namespace: "cozy-system"}
|
|
return d
|
|
}
|
|
rc, err := resourceConfigFrom([]v1alpha1.ApplicationDefinition{
|
|
mk("a-redis-shadow", "shadow-artifact", base.Add(time.Hour)),
|
|
mk("redis", "redis-artifact", base),
|
|
}, helmReleaseFlagValues{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(rc.Resources) != 1 || rc.Resources[0].Release.ChartRef.Name != "redis-artifact" {
|
|
t.Fatalf("resources = %+v, want the owner's redis-artifact only", rc.Resources)
|
|
}
|
|
}
|