From ca81b5f21800df3b2807195795a55196584b7c6c Mon Sep 17 00:00:00 2001 From: Astrid Gealer Date: Fri, 16 Oct 2026 03:04:57 +0100 Subject: [PATCH] fix(cmd): skip extended-support lifecycle entries with no start date isInExtendedSupport compared now against a zero LifecycleSupportStartDate, which never blocks, so an extended-support entry with both dates missing was silently read as "currently in extended support" and the instance was cut from the RDS recommendation count. Such an entry is now skipped with a warning naming the engine and major version. If no other entry proves extended support is active, the lookup reports known=false, so the caller keeps the instance in the count and emits its existing deduplicated "No lifecycle data" warning. Closes #1315 Co-Authored-By: Claude Opus 5.5 --- cmd/multi_service_engine_versions.go | 14 ++- cmd/multi_service_engine_versions_test.go | 132 ++++++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) diff --git a/cmd/multi_service_engine_versions.go b/cmd/multi_service_engine_versions.go index a27b80a72..97c8c05a5 100644 --- a/cmd/multi_service_engine_versions.go +++ b/cmd/multi_service_engine_versions.go @@ -420,6 +420,9 @@ func extractNumericPrefix(s string) string { // lookup: known is false when the major version cannot be extracted or the // engine:majorVersion key is missing from versionInfo. Callers on money paths // must not treat known=false as "not in extended support" (issue #1313). +// An extended-support entry with no start date is an anomalous payload: it is +// skipped with a warning rather than read as "already started", and if no +// other entry proves extended support is active, known is false (issue #1315). func isInExtendedSupport(engine, fullVersion string, versionInfo map[string]MajorEngineVersionInfo) (extended, known bool) { majorVersion := extractMajorVersion(engine, fullVersion) if majorVersion == "" { @@ -439,10 +442,19 @@ func isInExtendedSupport(engine, fullVersion string, versionInfo map[string]Majo // Check if current date falls within extended support period now := time.Now() + missingStart := false for _, lifecycle := range info.SupportedEngineLifecycles { if lifecycle.LifecycleSupportName != string(rdstypes.LifecycleSupportNameOpenSourceRdsExtendedSupport) { continue } + // A zero start date would pass the start gate below and claim the + // window is open with no evidence; skip it loudly (issue #1315). + if lifecycle.LifecycleSupportStartDate.IsZero() { + missingStart = true + log.Printf("⚠️ Extended-support lifecycle for %s %s has no start date: ignoring the entry, extended-support window unknown", + normalizedEngine, majorVersion) + continue + } // Not in extended support before its start date if now.Before(lifecycle.LifecycleSupportStartDate) { continue @@ -454,7 +466,7 @@ func isInExtendedSupport(engine, fullVersion string, versionInfo map[string]Majo } } - return false, true + return false, !missingStart } // adjustRecommendationForExcludedVersions reduces the instance count in a recommendation diff --git a/cmd/multi_service_engine_versions_test.go b/cmd/multi_service_engine_versions_test.go index ef7fe2380..6d4bb3078 100644 --- a/cmd/multi_service_engine_versions_test.go +++ b/cmd/multi_service_engine_versions_test.go @@ -744,6 +744,91 @@ func TestIsInExtendedSupport(t *testing.T) { expected: true, expectedKnown: true, }, + { + // Issue #1315: both dates absent must not read as "in extended support". + name: "Zero start and zero end date - unknown, not silently true", + engine: "mysql", + version: "5.7.44", + versionInfo: map[string]MajorEngineVersionInfo{ + "mysql:5.7": { + Engine: "mysql", + MajorEngineVersion: "5.7", + SupportedEngineLifecycles: []EngineLifecycleInfo{ + { + LifecycleSupportName: string(rdstypes.LifecycleSupportNameOpenSourceRdsExtendedSupport), + }, + }, + }, + }, + expected: false, + expectedKnown: false, + }, + { + // Issue #1315: a missing start date is skipped even with a future end date. + name: "Zero start date with future end date - unknown, not silently true", + engine: "mysql", + version: "5.7.44", + versionInfo: map[string]MajorEngineVersionInfo{ + "mysql:5.7": { + Engine: "mysql", + MajorEngineVersion: "5.7", + SupportedEngineLifecycles: []EngineLifecycleInfo{ + { + LifecycleSupportName: string(rdstypes.LifecycleSupportNameOpenSourceRdsExtendedSupport), + LifecycleSupportEndDate: futureDate, + }, + }, + }, + }, + expected: false, + expectedKnown: false, + }, + { + // A well-formed active entry still proves extended support even + // when another extended entry is missing its start date. + name: "Zero start entry alongside a valid active entry", + engine: "mysql", + version: "5.7.44", + versionInfo: map[string]MajorEngineVersionInfo{ + "mysql:5.7": { + Engine: "mysql", + MajorEngineVersion: "5.7", + SupportedEngineLifecycles: []EngineLifecycleInfo{ + { + LifecycleSupportName: string(rdstypes.LifecycleSupportNameOpenSourceRdsExtendedSupport), + LifecycleSupportEndDate: futureDate, + }, + { + LifecycleSupportName: string(rdstypes.LifecycleSupportNameOpenSourceRdsExtendedSupport), + LifecycleSupportStartDate: pastDate, + LifecycleSupportEndDate: futureDate, + }, + }, + }, + }, + expected: true, + expectedKnown: true, + }, + { + // A zero start date on a non-extended entry is irrelevant. + name: "Zero start date on standard support entry is ignored", + engine: "mysql", + version: "8.0.35", + versionInfo: map[string]MajorEngineVersionInfo{ + "mysql:8.0": { + Engine: "mysql", + MajorEngineVersion: "8.0", + SupportedEngineLifecycles: []EngineLifecycleInfo{ + { + LifecycleSupportName: string(rdstypes.LifecycleSupportNameOpenSourceRdsStandardSupport), + LifecycleSupportEndDate: futureDate, + }, + }, + }, + }, + expected: false, + expectedKnown: true, + }, { name: "Engine name normalization with spaces", engine: "Aurora MySQL", @@ -775,6 +860,53 @@ func TestIsInExtendedSupport(t *testing.T) { } } +// TestIsInExtendedSupport_ZeroStartDateWarns covers issue #1315: an +// extended-support entry with no start date is logged with the engine and +// major version, and the instance stays in the recommendation count. +func TestIsInExtendedSupport_ZeroStartDateWarns(t *testing.T) { + versionInfo := map[string]MajorEngineVersionInfo{ + "mysql:5.7": { + Engine: "mysql", + MajorEngineVersion: "5.7", + SupportedEngineLifecycles: []EngineLifecycleInfo{ + { + LifecycleSupportName: string(rdstypes.LifecycleSupportNameOpenSourceRdsExtendedSupport), + LifecycleSupportEndDate: time.Now().AddDate(1, 0, 0), + }, + }, + }, + } + recommendation := common.Recommendation{ + Service: common.ServiceRDS, + Region: "us-east-1", + ResourceType: "db.r5.large", + Count: 2, + Details: &common.DatabaseDetails{Engine: "MySQL"}, + } + instanceVersions := map[string][]InstanceEngineVersion{ + "db.r5.large": { + {Engine: "mysql", EngineVersion: "5.7.44", InstanceClass: "db.r5.large", Region: "us-east-1"}, + }, + } + + var logBuf bytes.Buffer + origOutput := log.Writer() + origFlags := log.Flags() + log.SetOutput(&logBuf) + log.SetFlags(0) + defer func() { + log.SetOutput(origOutput) + log.SetFlags(origFlags) + }() + + result := adjustRecommendationForExcludedVersions(recommendation, instanceVersions, versionInfo) + + assert.Equal(t, 2, result.Count, "missing start date must not exclude the instance") + logged := logBuf.String() + assert.Contains(t, logged, "mysql 5.7 has no start date", "warning must name the engine and major version") + assert.Contains(t, logged, "No lifecycle data for mysql 5.7.44", "caller must surface the unknown status") +} + func TestQueryMajorEngineVersions_ErrorHandling(t *testing.T) { // This test validates the error handling logic when AWS config fails ctx := context.Background()