Skip to content

Commit e7bc7f3

Browse files
committed
fix: nil pointer dereference in CapacityReservationFromEC2 for Interruptible field
lo.Ternary eagerly evaluates all arguments in Go, so lo.Ternary(cr.Interruptible == nil, false, *cr.Interruptible) dereferences *cr.Interruptible unconditionally, panicking when the pointer is nil. This is the default for all standard ODCRs and capacity blocks returned by the EC2 API. Replace with lo.FromPtrOr which safely returns the default value when the pointer is nil. Also adds a test case with nil Interruptible to prevent regression. Fixes #9079
1 parent 0bac631 commit e7bc7f3

File tree

2 files changed

+27
-3
lines changed

2 files changed

+27
-3
lines changed

pkg/apis/v1/ec2nodeclass_status.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ func CapacityReservationFromEC2(clk clock.Clock, cr *ec2types.CapacityReservatio
263263
InstanceType: *cr.InstanceType,
264264
OwnerID: *cr.OwnerId,
265265
ReservationType: reservationType,
266-
Interruptible: lo.Ternary(cr.Interruptible == nil, false, *cr.Interruptible),
266+
Interruptible: lo.FromPtrOr(cr.Interruptible, false),
267267
State: state,
268268
}, nil
269269
}

pkg/controllers/nodeclass/capacityreservation_test.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,33 @@ var _ = Describe("NodeClass Capacity Reservation Reconciler", func() {
131131
ReservationType: ec2types.CapacityReservationTypeDefault,
132132
Interruptible: lo.ToPtr(true),
133133
},
134+
{
135+
AvailabilityZone: lo.ToPtr("test-zone-1a"),
136+
InstanceType: lo.ToPtr("m5.large"),
137+
OwnerId: lo.ToPtr(selfOwnerID),
138+
InstanceMatchCriteria: ec2types.InstanceMatchCriteriaTargeted,
139+
CapacityReservationId: lo.ToPtr("cr-nil-interruptible"),
140+
AvailableInstanceCount: lo.ToPtr[int32](5),
141+
State: ec2types.CapacityReservationStateActive,
142+
ReservationType: ec2types.CapacityReservationTypeDefault,
143+
// Interruptible intentionally omitted (nil) — this is the default
144+
// for standard ODCRs and capacity blocks returned by the EC2 API
145+
},
134146
},
135147
})
136148
})
149+
It("should handle capacity reservations with nil Interruptible field", func() {
150+
nodeClass.Spec.CapacityReservationSelectorTerms = append(nodeClass.Spec.CapacityReservationSelectorTerms, v1.CapacityReservationSelectorTerm{
151+
ID: "cr-nil-interruptible",
152+
})
153+
ExpectApplied(ctx, env.Client, nodeClass)
154+
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
155+
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
156+
Expect(nodeClass.StatusConditions().Get(v1.ConditionTypeCapacityReservationsReady).IsTrue()).To(BeTrue())
157+
Expect(nodeClass.Status.CapacityReservations).To(HaveLen(1))
158+
Expect(nodeClass.Status.CapacityReservations[0].ID).To(Equal("cr-nil-interruptible"))
159+
Expect(nodeClass.Status.CapacityReservations[0].Interruptible).To(BeFalse())
160+
})
137161
It("should resolve capacity reservations by ID", func() {
138162
const targetID = "cr-m5.large-1a-1"
139163
nodeClass.Spec.CapacityReservationSelectorTerms = append(nodeClass.Spec.CapacityReservationSelectorTerms, v1.CapacityReservationSelectorTerm{
@@ -206,10 +230,10 @@ var _ = Describe("NodeClass Capacity Reservation Reconciler", func() {
206230
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
207231
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
208232
Expect(nodeClass.StatusConditions().Get(v1.ConditionTypeCapacityReservationsReady).IsTrue()).To(BeTrue())
209-
Expect(nodeClass.Status.CapacityReservations).To(HaveLen(5))
233+
Expect(nodeClass.Status.CapacityReservations).To(HaveLen(6))
210234
Expect(lo.Map(nodeClass.Status.CapacityReservations, func(cr v1.CapacityReservation, _ int) string {
211235
return cr.ID
212-
})).To(ContainElements("cr-m5.large-1a-1", "cr-m5.large-1a-2", "cr-p5.48xlarge-1a", "cr-m5.large-1b-1", "cr-m5.large-1b-2"))
236+
})).To(ContainElements("cr-m5.large-1a-1", "cr-m5.large-1a-2", "cr-p5.48xlarge-1a", "cr-m5.large-1b-1", "cr-m5.large-1b-2", "cr-nil-interruptible"))
213237
for _, cr := range nodeClass.Status.CapacityReservations {
214238
Expect(cr.InstanceMatchCriteria).To(Equal("targeted"))
215239
}

0 commit comments

Comments
 (0)