Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 162 additions & 3 deletions deploy/endoflife-override/api/amazon-aurora-mysql.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,164 @@
[
{"cycle":"3","releaseDate":"2021-11-23","eol":"2028-04-30","latest":"3.12.0","latestReleaseDate":"2026-02-17","lts":false,"extendedSupport":"2029-07-31"},
{"cycle":"2","releaseDate":"2018-02-06","eol":"2024-10-31","latest":"2.12.5","latestReleaseDate":"2025-04-09","lts":false,"extendedSupport":"2027-02-28"},
{"cycle":"1","releaseDate":"2015-07-27","eol":"2023-02-28","latest":"1.23.4","latestReleaseDate":"2022-08-11","lts":false,"extendedSupport":false}
{
"cycle": "8.4.8",
"releaseDate": "2026-09-03",
"eol": "2028-03-31",
"latest": "8.4.8",
"latestReleaseDate": "2026-09-03",
"lts": false,
"extendedSupport": false
},
{
"cycle": "8.4.7",
"releaseDate": "2026-05-21",
"eol": "2027-11-30",
"latest": "8.4.7",
"latestReleaseDate": "2026-05-21",
"lts": false,
"extendedSupport": false
},
{
"cycle": "8.4",
"releaseDate": "2026-05-21",
"eol": "2032-04-30",
"latest": "8.4.8",
"latestReleaseDate": "2026-09-03",
"lts": false,
"extendedSupport": true
},
{
"cycle": "3.13",
"releaseDate": "2026-08-27",
"eol": "2027-08-27",
"latest": "3.13.0",
"latestReleaseDate": "2026-08-27",
"lts": false,
"extendedSupport": false
},
{
"cycle": "3.12",
"releaseDate": "2026-02-17",
"eol": "2027-02-17",
"latest": "3.12.0",
"latestReleaseDate": "2026-02-17",
"lts": false,
"extendedSupport": false
},
{
"cycle": "3.11",
"releaseDate": "2025-11-13",
"eol": "2026-11-13",
"latest": "3.11.1",
"latestReleaseDate": "2025-12-16",
"lts": false,
"extendedSupport": false
},
{
"cycle": "3.10",
"releaseDate": "2025-07-31",
"eol": "2028-04-30",
"latest": "3.10.5",
"latestReleaseDate": "2026-07-30",
"lts": true,
"extendedSupport": false
},
{
"cycle": "3.09",
"releaseDate": "2025-05-14",
"eol": "2026-08-31",
"latest": "3.09.0",
"latestReleaseDate": "2025-05-14",
"lts": false,
"extendedSupport": false
},
{
"cycle": "3.08",
"releaseDate": "2024-11-18",
"eol": "2026-08-31",
"latest": "3.08.2",
"latestReleaseDate": "2025-04-07",
"lts": false,
"extendedSupport": false
},
{
"cycle": "3.07",
"releaseDate": "2024-06-04",
"eol": "2025-08-31",
"latest": "3.07.1",
"latestReleaseDate": "2024-07-31",
"lts": false,
"extendedSupport": false
},
{
"cycle": "3.06",
"releaseDate": "2024-03-07",
"eol": "2025-08-31",
"latest": "3.06.1",
"latestReleaseDate": "2024-07-31",
"lts": false,
"extendedSupport": false
},
{
"cycle": "3.05",
"releaseDate": "2023-10-25",
"eol": "2025-08-31",
"latest": "3.05.2",
"latestReleaseDate": "2024-01-31",
"lts": false,
"extendedSupport": false
},
{
"cycle": "3.04",
"releaseDate": "2023-07-31",
"eol": "2026-10-31",
"latest": "3.04.6",
"latestReleaseDate": "2026-01-02",
"lts": true,
"extendedSupport": false
},
{
"cycle": "3",
"releaseDate": "2021-11-23",
"eol": "2028-04-30",
"latest": "3.13.0",
"latestReleaseDate": "2026-08-27",
"lts": false,
"extendedSupport": "2029-07-31"
},
{
"cycle": "2.12",
"releaseDate": "2023-07-25",
"eol": "2024-10-31",
"latest": "2.12.5",
"latestReleaseDate": "2025-04-09",
"lts": false,
"extendedSupport": "2029-06-30"
},
{
"cycle": "2.11",
"releaseDate": "2022-10-25",
"eol": "2024-10-31",
"latest": "2.11.6",
"latestReleaseDate": "2024-07-19",
"lts": false,
"extendedSupport": "2029-06-30"
},
{
"cycle": "2",
"releaseDate": "2018-02-06",
"eol": "2024-10-31",
"latest": "2.12.5",
"latestReleaseDate": "2025-04-09",
"lts": false,
"extendedSupport": "2029-06-30"
},
{
"cycle": "1",
"releaseDate": "2015-07-27",
"eol": "2023-02-28",
"latest": "1.23.4",
"latestReleaseDate": "2022-08-11",
"lts": false,
"extendedSupport": false
}
]
87 changes: 87 additions & 0 deletions deploy/endoflife-override/aurora_mysql_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package override

import (
"context"
"encoding/json"
"os"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/block/Version-Guard/pkg/eol/endoflife"
"github.com/block/Version-Guard/pkg/types"
)

// Aurora MySQL minor versions carry their own end-of-standard-support dates
// that arrive well before the major cycle's date. Before per-minor cycles
// existed, every 3.x cluster resolved to cycle "3" (EOL 2028-04-30) and a
// fleet on 3.04 (EOL 2026-10-31) reported GREEN. This test loads the
// repository override through the real provider and checks that the bare
// version strings Wiz reports resolve to the minor cycle, and that a minor
// without paid extended support terminates on its own date rather than
// inheriting the major's.
func TestAuroraMySQLOverrideResolvesMinorCycles(t *testing.T) {
provider := auroraMySQLOverrideProvider(t)

tests := []struct {
version string
wantCycle string
wantEOL string // terminal EOLDate
wantStandardEnd string // DeprecationDate; empty when standard end == EOL
}{
{version: "3.04.2", wantCycle: "3.04", wantEOL: "2026-10-31"},
{version: "3.04.6", wantCycle: "3.04", wantEOL: "2026-10-31"},
// Deprecated non-LTS minors must not fall back to the major's 2028 date.
{version: "3.06.0", wantCycle: "3.06", wantEOL: "2025-08-31"},
{version: "3.07.1", wantCycle: "3.07", wantEOL: "2025-08-31"},
{version: "3.08.2", wantCycle: "3.08", wantEOL: "2026-08-31"},
{version: "3.10.1", wantCycle: "3.10", wantEOL: "2028-04-30"},
{version: "3.11.1", wantCycle: "3.11", wantEOL: "2026-11-13"},
{version: "8.4.7", wantCycle: "8.4.7", wantEOL: "2027-11-30"},
// 2.11/2.12 stay available under RDS Extended Support.
{version: "2.11.5", wantCycle: "2.11", wantEOL: "2029-06-30", wantStandardEnd: "2024-10-31"},
{version: "2.12.5", wantCycle: "2.12", wantEOL: "2029-06-30", wantStandardEnd: "2024-10-31"},
// Minor without its own cycle falls back to the major.
{version: "3.99.0", wantCycle: "3", wantEOL: "2029-07-31", wantStandardEnd: "2028-04-30"},
}

for _, tt := range tests {
t.Run(tt.version, func(t *testing.T) {
lifecycle, err := provider.GetVersionLifecycle(context.Background(), "aurora-mysql", tt.version)
require.NoError(t, err)
require.Equal(t, tt.wantCycle, lifecycle.Version, "matched cycle")
require.NotNil(t, lifecycle.EOLDate)
require.Equal(t, tt.wantEOL, lifecycle.EOLDate.Format("2006-01-02"), "EOLDate")
if tt.wantStandardEnd == "" {
require.Nil(t, lifecycle.DeprecationDate, "no separate standard-support end")
require.Nil(t, lifecycle.ExtendedSupportEnd, "no extended support")
return
}
require.NotNil(t, lifecycle.DeprecationDate)
require.Equal(t, tt.wantStandardEnd, lifecycle.DeprecationDate.Format("2006-01-02"), "DeprecationDate")
})
}
}

func auroraMySQLOverrideProvider(t *testing.T) *endoflife.Provider {
t.Helper()
data, err := os.ReadFile("api/amazon-aurora-mysql.json")
require.NoError(t, err)
var cycles []*endoflife.ProductCycle
require.NoError(t, json.Unmarshal(data, &cycles))

client := &endoflife.MockClient{
GetProductCyclesFunc: func(_ context.Context, product string) (endoflife.ProductCyclesResult, error) {
require.Equal(t, "amazon-aurora-mysql", product)
return endoflife.ProductCyclesResult{
Cycles: cycles,
FetchedAt: time.Now(),
DataSource: types.LifecycleDataSourceLocalOverride,
}, nil
},
}
provider, err := endoflife.NewProvider(client, "amazon-aurora-mysql", "standard", time.Hour, nil)
require.NoError(t, err)
return provider
}
8 changes: 4 additions & 4 deletions deploy/endoflife-override/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
{
"product": "amazon-aurora-mysql",
"path": "api/amazon-aurora-mysql.json",
"reason": "Product pending upstream inclusion",
"reason": "Upstream only publishes major cycles; Aurora MySQL minors (3.04, 3.08, ...) reach end of standard support years before the major, so per-minor cycles from the AWS release calendar are added here",
"owner": "@block/block-platform",
"source_url": "https://github.com/endoflife-date/endoflife.date/pull/9534",
"reviewed_on": "2026-08-05",
"review_due_on": "2026-09-04"
"source_url": "https://docs.aws.amazon.com/AmazonRDS/latest/AuroraMySQLReleaseNotes/AuroraMySQL.release-calendars.html",
"reviewed_on": "2026-09-30",
"review_due_on": "2026-10-30"
},
{
"product": "amazon-opensearch",
Expand Down
57 changes: 51 additions & 6 deletions deploy/endoflife-override/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func TestValidateManifest(t *testing.T) {
{name: "invalid source URL", mutate: mutateManifest(func(m *manifest) { m.Overrides[0].SourceURL = "http://example.com/source" }), wantErr: "must use https"},
{name: "malformed source URL", mutate: mutateManifest(func(m *manifest) { m.Overrides[0].SourceURL = "https://[invalid" }), wantErr: "valid https URL"},
{name: "invalid review date", mutate: mutateManifest(func(m *manifest) { m.Overrides[0].ReviewedOn = "August 5" }), wantErr: "YYYY-MM-DD"},
{name: "review due before reviewed", mutate: mutateManifest(func(m *manifest) { m.Overrides[0].ReviewDueOn = "2026-08-04" }), wantErr: "before reviewed_on"},
{name: "review interval over 30 days", mutate: mutateManifest(func(m *manifest) { m.Overrides[0].ReviewDueOn = "2026-09-05" }), wantErr: "exceeds 30 days"},
{name: "review due before reviewed", mutate: mutateManifest(func(m *manifest) { m.Overrides[0].ReviewDueOn = shiftDate(t, m.Overrides[0].ReviewedOn, -1) }), wantErr: "before reviewed_on"},
{name: "review interval over 30 days", mutate: mutateManifest(func(m *manifest) { m.Overrides[0].ReviewDueOn = shiftDate(t, m.Overrides[0].ReviewedOn, 31) }), wantErr: "exceeds 30 days"},
{name: "path escapes API directory", mutate: mutateManifest(func(m *manifest) { m.Overrides[0].Path = "api/../manifest.json" }), wantErr: "direct api/"},
{name: "nested API path", mutate: func(t *testing.T, root string) {
nested := filepath.Join(root, "api", "nested", "amazon-aurora-mysql.json")
Expand Down Expand Up @@ -99,17 +99,22 @@ func TestValidateManifest(t *testing.T) {
data = append(data, []byte("\n{}")...)
require.NoError(t, os.WriteFile(path, data, 0o600))
}, wantErr: "multiple JSON values"},
{name: "overdue review warns", mutate: mutateManifest(func(m *manifest) { m.Overrides[0].ReviewDueOn = "2026-08-06" }), wantWarning: "review overdue"},
{name: "overdue review warns", mutate: mutateManifest(func(m *manifest) { m.Overrides[0].ReviewDueOn = shiftDate(t, m.Overrides[0].ReviewedOn, 1) }), wantWarning: "review overdue"},
}

// Two days after the first override was reviewed: inside its review window,
// but past a due date moved to the following day.
reviewedOn, _ := repositoryReviewDates(t)
now := reviewedOn.AddDate(0, 0, 2)

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
root := copyRepositoryFixture(t)
if tt.mutate != nil {
tt.mutate(t, root)
}
var warnings bytes.Buffer
err := validateManifest(root, time.Date(2026, 8, 7, 0, 0, 0, 0, time.UTC), &warnings)
err := validateManifest(root, now, &warnings)
if tt.wantErr != "" {
require.ErrorContains(t, err, tt.wantErr)
return
Expand All @@ -121,13 +126,15 @@ func TestValidateManifest(t *testing.T) {
}

func TestValidateManifestReviewDueDateBoundary(t *testing.T) {
// The earliest due date is the first moment any override can be overdue.
reviewDueOn := earliestReviewDueDate(t)
tests := []struct {
name string
now time.Time
wantWarning string
}{
{name: "due date remains valid for full UTC day", now: time.Date(2026, 9, 4, 23, 59, 59, 0, time.UTC)},
{name: "following UTC day warns", now: time.Date(2026, 9, 5, 0, 0, 0, 0, time.UTC), wantWarning: "review overdue"},
{name: "due date remains valid for full UTC day", now: reviewDueOn.Add(24*time.Hour - time.Second)},
{name: "following UTC day warns", now: reviewDueOn.AddDate(0, 0, 1), wantWarning: "review overdue"},
}

for _, tt := range tests {
Expand Down Expand Up @@ -161,6 +168,44 @@ func mutateManifest(mutate func(*manifest)) func(*testing.T, string) {
}
}

// repositoryReviewDates returns the first override's reviewed_on and
// review_due_on so date-sensitive cases track the manifest as it is reviewed.
func repositoryReviewDates(t *testing.T) (reviewedOn, reviewDueOn time.Time) {
t.Helper()
m, err := readManifest("manifest.json")
require.NoError(t, err)
require.NotEmpty(t, m.Overrides)
reviewedOn, err = time.Parse(dateLayout, m.Overrides[0].ReviewedOn)
require.NoError(t, err)
reviewDueOn, err = time.Parse(dateLayout, m.Overrides[0].ReviewDueOn)
require.NoError(t, err)
return reviewedOn, reviewDueOn
}

// earliestReviewDueDate returns the soonest review_due_on across all overrides.
func earliestReviewDueDate(t *testing.T) time.Time {
t.Helper()
m, err := readManifest("manifest.json")
require.NoError(t, err)
require.NotEmpty(t, m.Overrides)
var earliest time.Time
for _, override := range m.Overrides {
due, err := time.Parse(dateLayout, override.ReviewDueOn)
require.NoError(t, err)
if earliest.IsZero() || due.Before(earliest) {
earliest = due
}
}
return earliest
}

func shiftDate(t *testing.T, value string, days int) string {
t.Helper()
parsed, err := time.Parse(dateLayout, value)
require.NoError(t, err)
return parsed.AddDate(0, 0, days).Format(dateLayout)
}

func copyRepositoryFixture(t *testing.T) string {
t.Helper()
root := t.TempDir()
Expand Down
Loading