@@ -125,6 +125,68 @@ spec:
125125 }
126126}
127127
128+ // TestParseCRDsToleratesTemplatedNeighborDocument reproduces the cozyplane
129+ // bug: a real CRD living in a chart's templates/ dir, guarded by a
130+ // `{{- if }}` block that is not valid YAML on its own. The malformed
131+ // neighbor document must not take the real CRD down with it.
132+ func TestParseCRDsToleratesTemplatedNeighborDocument (t * testing.T ) {
133+ const doc = `{{- /* CRDs are installed only in CRD mode */ -}}
134+ {{- if not .Values.apiserver.enabled }}
135+ ---
136+ apiVersion: apiextensions.k8s.io/v1
137+ kind: CustomResourceDefinition
138+ metadata:
139+ name: vpcs.sdn.cozystack.io
140+ spec:
141+ group: sdn.cozystack.io
142+ names: {kind: VPC, plural: vpcs}
143+ versions:
144+ - name: v1alpha1
145+ served: true
146+ schema: {openAPIV3Schema: {type: object}}
147+ {{- end }}
148+ `
149+ rs , err := ParseCRDs ("templates/crds.yaml" , []byte (doc ))
150+ if err != nil {
151+ t .Fatalf ("ParseCRDs failed: %v" , err )
152+ }
153+ if len (rs ) != 1 || rs [0 ].Plural != "vpcs" {
154+ t .Fatalf ("expected the CRD sharing the file with templated markup to survive, got %v" , rs )
155+ }
156+ }
157+
158+ // TestParseAPIServices covers the new APIService source: a first-party group
159+ // registration is surfaced as a schema-less resource, and a non-APIService or
160+ // incomplete document is ignored.
161+ func TestParseAPIServices (t * testing.T ) {
162+ const doc = `apiVersion: apiregistration.k8s.io/v1
163+ kind: APIService
164+ metadata:
165+ name: v1alpha1.sdn.cozystack.io
166+ spec:
167+ group: sdn.cozystack.io
168+ version: v1alpha1
169+ groupPriorityMinimum: 1000
170+ versionPriority: 10
171+ ---
172+ apiVersion: v1
173+ kind: Service
174+ metadata:
175+ name: cozyplane-apiserver
176+ `
177+ rs := ParseAPIServices ("apiserver.yaml" , []byte (doc ))
178+ if len (rs ) != 1 {
179+ t .Fatalf ("expected exactly 1 APIService resource, got %v" , rs )
180+ }
181+ r := rs [0 ]
182+ if r .Group != "sdn.cozystack.io" || r .Source != SourceAPIService || r .Plural != apiServicePlural {
183+ t .Fatalf ("unexpected APIService resource: %+v" , r )
184+ }
185+ if _ , ok := r .Versions ["v1alpha1" ]; ! ok {
186+ t .Fatalf ("expected v1alpha1 in versions, got %v" , r .Versions )
187+ }
188+ }
189+
128190func TestParseAPIServerStorages (t * testing.T ) {
129191 src := `
130192 coreV1alpha1Storage["tenantsecrets"] = cozyregistry.RESTInPeace(x)
@@ -186,6 +248,160 @@ func TestLoadSnapshotDiscoversCRDsUnderChartsCrds(t *testing.T) {
186248 }
187249}
188250
251+ // TestLoadSnapshotDiscoversCRDUnderTemplatesDir reproduces the real gap found
252+ // in the cozyplane PR: a raw CRD document guarded by `{{- if }}`, sitting in a
253+ // chart's templates/ dir (not crds/), must still be discovered — directory
254+ // name is no longer the discovery signal, file content is.
255+ func TestLoadSnapshotDiscoversCRDUnderTemplatesDir (t * testing.T ) {
256+ root := t .TempDir ()
257+ write := func (rel , content string ) {
258+ p := filepath .Join (root , filepath .FromSlash (rel ))
259+ if err := os .MkdirAll (filepath .Dir (p ), 0o755 ); err != nil {
260+ t .Fatal (err )
261+ }
262+ if err := os .WriteFile (p , []byte (content ), 0o644 ); err != nil {
263+ t .Fatal (err )
264+ }
265+ }
266+ write ("packages/system/cozyplane/templates/crds.yaml" , `{{- if not .Values.apiserver.enabled }}
267+ ---
268+ apiVersion: apiextensions.k8s.io/v1
269+ kind: CustomResourceDefinition
270+ metadata:
271+ name: vpcs.sdn.cozystack.io
272+ spec:
273+ group: sdn.cozystack.io
274+ names: {kind: VPC, plural: vpcs}
275+ versions:
276+ - name: v1alpha1
277+ served: true
278+ schema: {openAPIV3Schema: {type: object}}
279+ {{- end }}
280+ ` )
281+
282+ snap , err := LoadSnapshot (root )
283+ if err != nil {
284+ t .Fatal (err )
285+ }
286+ if _ , ok := snap [resourceKey {Group : "sdn.cozystack.io" , Plural : "vpcs" }]; ! ok {
287+ t .Fatalf ("CRD under templates/ was not discovered: %v" , snap )
288+ }
289+ }
290+
291+ // TestLoadSnapshotDiscoversAPIServiceAndTripsNewGroup covers the other half of
292+ // the cozyplane gap: a group served only by an aggregated apiserver (no CRD,
293+ // no cozyrd, no apiserver.go entry) — its schema lives entirely outside this
294+ // repo, but the group's first appearance must still require review. Versions
295+ // registered via separate sibling APIService objects (one per version, the
296+ // real-world shape) must merge onto the same resource.
297+ func TestLoadSnapshotDiscoversAPIServiceAndTripsNewGroup (t * testing.T ) {
298+ root := t .TempDir ()
299+ write := func (rel , content string ) {
300+ p := filepath .Join (root , filepath .FromSlash (rel ))
301+ if err := os .MkdirAll (filepath .Dir (p ), 0o755 ); err != nil {
302+ t .Fatal (err )
303+ }
304+ if err := os .WriteFile (p , []byte (content ), 0o644 ); err != nil {
305+ t .Fatal (err )
306+ }
307+ }
308+ write ("packages/system/cozyplane/templates/apiserver-v1alpha1.yaml" , `apiVersion: apiregistration.k8s.io/v1
309+ kind: APIService
310+ metadata: {name: v1alpha1.sdn.cozystack.io}
311+ spec: {group: sdn.cozystack.io, version: v1alpha1}
312+ ` )
313+ write ("packages/system/cozyplane/templates/apiserver-v1beta1.yaml" , `apiVersion: apiregistration.k8s.io/v1
314+ kind: APIService
315+ metadata: {name: v1beta1.sdn.cozystack.io}
316+ spec: {group: sdn.cozystack.io, version: v1beta1}
317+ ` )
318+
319+ // A minimal but non-empty base, unrelated to sdn.cozystack.io.
320+ baseRoot := t .TempDir ()
321+ writeBase := func (rel , content string ) {
322+ p := filepath .Join (baseRoot , filepath .FromSlash (rel ))
323+ if err := os .MkdirAll (filepath .Dir (p ), 0o755 ); err != nil {
324+ t .Fatal (err )
325+ }
326+ if err := os .WriteFile (p , []byte (content ), 0o644 ); err != nil {
327+ t .Fatal (err )
328+ }
329+ }
330+ writeBase ("packages/system/postgres-rd/cozyrds/postgres.yaml" , sampleCozyRD )
331+
332+ base , err := LoadSnapshot (baseRoot )
333+ if err != nil {
334+ t .Fatal (err )
335+ }
336+ head , err := LoadSnapshot (root )
337+ if err != nil {
338+ t .Fatal (err )
339+ }
340+
341+ key := resourceKey {Group : "sdn.cozystack.io" , Plural : apiServicePlural }
342+ res , ok := head [key ]
343+ if ! ok {
344+ t .Fatalf ("APIService group was not discovered: %v" , head )
345+ }
346+ if _ , ok := res .Versions ["v1alpha1" ]; ! ok {
347+ t .Fatalf ("expected v1alpha1 merged in, got %v" , res .Versions )
348+ }
349+ if _ , ok := res .Versions ["v1beta1" ]; ! ok {
350+ t .Fatalf ("expected v1beta1 from the sibling APIService object merged in, got %v" , res .Versions )
351+ }
352+
353+ if countCategory (Classify (base , head ), NewGroup ) != 1 {
354+ t .Fatalf ("expected the APIService-only group to trip NewGroup, got %v" , Classify (base , head ))
355+ }
356+ }
357+
358+ // TestLoadSnapshotIgnoresThirdPartyManifestsUnderTemplates guards against the
359+ // content-based walk becoming too permissive: a vendored CRD and APIService
360+ // for a non-cozystack.io group, sitting in the exact same templates/ shape,
361+ // must still be filtered out.
362+ func TestLoadSnapshotIgnoresThirdPartyManifestsUnderTemplates (t * testing.T ) {
363+ root := t .TempDir ()
364+ write := func (rel , content string ) {
365+ p := filepath .Join (root , filepath .FromSlash (rel ))
366+ if err := os .MkdirAll (filepath .Dir (p ), 0o755 ); err != nil {
367+ t .Fatal (err )
368+ }
369+ if err := os .WriteFile (p , []byte (content ), 0o644 ); err != nil {
370+ t .Fatal (err )
371+ }
372+ }
373+ write ("packages/system/cert-manager/templates/crds.yaml" , `apiVersion: apiextensions.k8s.io/v1
374+ kind: CustomResourceDefinition
375+ metadata: {name: certificates.cert-manager.io}
376+ spec:
377+ group: cert-manager.io
378+ names: {kind: Certificate, plural: certificates}
379+ versions:
380+ - name: v1
381+ served: true
382+ schema: {openAPIV3Schema: {type: object}}
383+ ` )
384+ write ("packages/system/metrics/templates/apiservice.yaml" , `apiVersion: apiregistration.k8s.io/v1
385+ kind: APIService
386+ metadata: {name: v1beta1.metrics.k8s.io}
387+ spec: {group: metrics.k8s.io, version: v1beta1}
388+ ` )
389+ // Give the checkout a first-party resource so LoadSnapshot's
390+ // empty-snapshot guard doesn't fire for an unrelated reason.
391+ write ("packages/system/postgres-rd/cozyrds/postgres.yaml" , sampleCozyRD )
392+
393+ snap , err := LoadSnapshot (root )
394+ if err != nil {
395+ t .Fatal (err )
396+ }
397+ if _ , ok := snap [resourceKey {Group : "cert-manager.io" , Plural : "certificates" }]; ok {
398+ t .Fatalf ("vendored third-party CRD must not be discovered: %v" , snap )
399+ }
400+ if _ , ok := snap [resourceKey {Group : "metrics.k8s.io" , Plural : apiServicePlural }]; ok {
401+ t .Fatalf ("vendored third-party APIService must not be discovered: %v" , snap )
402+ }
403+ }
404+
189405// TestLoadSnapshotEmptyErrors covers the silent-bypass guard: an empty result
190406// (e.g. wrong directory) must be an error, not a clean pass.
191407func TestLoadSnapshotEmptyErrors (t * testing.T ) {
0 commit comments