Skip to content

Commit deb0ce9

Browse files
tool/scaffolder: fixed missed nil-pointer checks for getDependencies method
1 parent d35b4a8 commit deb0ce9

File tree

3 files changed

+428
-15
lines changed

3 files changed

+428
-15
lines changed

tools/scaffolder/internal/generate/indexers.go

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,7 @@ type ReferenceField struct {
3939
ReferencedKind string
4040
ReferencedGroup string
4141
ReferencedVersion string
42-
// RequiredSegments tracks whether each meaningful path segment is required.
43-
// The slice aligns with the meaningful parts of FieldPath (excluding "properties", "items").
44-
// For example, for FieldPath "properties.spec.properties.groupRef",
45-
// RequiredSegments[0] indicates if "spec" is required, RequiredSegments[1] if "groupRef" is required.
46-
RequiredSegments []bool
42+
RequiredSegments []bool
4743
}
4844

4945
type IndexerInfo struct {
@@ -473,17 +469,19 @@ func generateFieldExtractionCode(fields []ReferenceField) []jen.Code {
473469
// We need to convert this to: resource.Spec.<version>.GroupRef
474470
fieldAccessPath := buildFieldAccessPath(field.FieldPath)
475471

476-
// Generate: if resource.Spec.<version>.GroupRef != nil && resource.Spec.<version>.GroupRef.Name != "" {
477-
// keys = append(keys, types.NamespacedName{Name: resource.Spec.<version>.GroupRef.Name, Namespace: resource.Namespace}.String())
472+
// Nil check if conditions
473+
nilCheckCondition := buildNilCheckConditions(fieldAccessPath, field.RequiredSegments)
474+
475+
// Add check that the Name field is not empty
476+
condition := nilCheckCondition.Op("&&").Add(
477+
jen.Id(fieldAccessPath).Dot("Name").Op("!=").Lit(""),
478+
)
479+
480+
// Generate: if <nil checks> && resource.Spec.<version>.GroupRef.Name != "" {
481+
// keys = append(keys, types.NamespacedName{...}.String())
478482
// }
479483
code = append(code,
480-
jen.If(
481-
jen.Op("").Add(
482-
jen.Id(fieldAccessPath).Op("!=").Nil(),
483-
).Op("&&").Add(
484-
jen.Id(fieldAccessPath).Dot("Name").Op("!=").Lit(""),
485-
),
486-
).Block(
484+
jen.If(condition).Block(
487485
jen.Id("keys").Op("=").Append(
488486
jen.Id("keys"),
489487
jen.Qual("k8s.io/apimachinery/pkg/types", "NamespacedName").Values(jen.Dict{
@@ -524,6 +522,71 @@ func capitalizeFirst(s string) string {
524522
return strings.ToUpper(s[:1]) + s[1:]
525523
}
526524

525+
// buildNilCheckConditions creates a compound nil check condition for a field access path
526+
// based on which segments are required (non-pointer) vs optional (pointer).
527+
// Examples:
528+
// - fieldAccessPath: "cluster.Spec.V20250312.GroupRef"
529+
// - requiredSegments: [false, true, false] (for Spec, V20250312, GroupRef - excludes variable name)
530+
func buildNilCheckConditions(fieldAccessPath string, requiredSegments []bool) *jen.Statement {
531+
segments := strings.Split(fieldAccessPath, ".")
532+
533+
if len(requiredSegments) == 0 {
534+
return buildDotChain(segments).Op("!=").Nil()
535+
}
536+
537+
if len(requiredSegments) != len(segments)-1 {
538+
return buildDotChain(segments).Op("!=").Nil()
539+
}
540+
541+
var conditions *jen.Statement
542+
543+
for i := 1; i < len(segments); i++ {
544+
requiredIndex := i - 1
545+
546+
// Skip if segment is required, means it's a non-pointer struct
547+
if requiredSegments[requiredIndex] {
548+
continue
549+
}
550+
551+
// Special case: "Spec" is always a non-pointer struct in Kubernetes resources
552+
if segments[i] == "Spec" {
553+
continue
554+
}
555+
556+
// Build the path up to this segment
557+
pathSegments := segments[:i+1]
558+
nilCheck := buildDotChain(pathSegments).Op("!=").Nil()
559+
560+
if conditions == nil {
561+
conditions = nilCheck
562+
} else {
563+
conditions = conditions.Op("&&").Add(nilCheck)
564+
}
565+
}
566+
567+
// If no conditions were added (all segments required), check only the last field
568+
if conditions == nil {
569+
return buildDotChain(segments).Op("!=").Nil()
570+
}
571+
572+
return conditions
573+
}
574+
575+
// buildDotChain creates a jen.Statement for a dot-separated path
576+
// For example: ["cluster", "Spec", "V20250312"] -> jen.Id("cluster").Dot("Spec").Dot("V20250312")
577+
func buildDotChain(segments []string) *jen.Statement {
578+
if len(segments) == 0 {
579+
return jen.Null()
580+
}
581+
582+
stmt := jen.Id(segments[0])
583+
for i := 1; i < len(segments); i++ {
584+
stmt = stmt.Dot(segments[i])
585+
}
586+
587+
return stmt
588+
}
589+
527590
func generateMapFunc(f *jen.File, crdKind string, indexer IndexerInfo) {
528591
f.Func().
529592
Id(fmt.Sprintf("New%sBy%sMapFunc", crdKind, indexer.TargetKind)).

0 commit comments

Comments
 (0)