From 8a92347c61671fd2bc6f5f3d06408ebd27088cae Mon Sep 17 00:00:00 2001 From: Chai Bot Date: Mon, 24 Aug 2026 16:26:21 +0000 Subject: [PATCH] Fix development_start parsing for Sippy date-only format The Sippy /api/releases endpoint switched the development_start and ga date fields from RFC 3339 timestamps (e.g. "2018-07-11T00:00:00Z") to bare date-only strings (e.g. "2018-07-11"). The existing *time.Time fields cannot unmarshal the new format, so getLatestRelease() fails to parse the response. Add a flexibleDateTime type that wraps time.Time with a custom UnmarshalJSON. It tries the RFC 3339 layout first and falls back to the date-only layout ("2006-01-02"), handling null and empty values gracefully. Both the GA and DevelopmentStart fields now use this type so the tool remains compatible with old and new Sippy responses. This is a manual backport of the fix from master (openshift/api#2998) to release-4.22; the cherry-pick did not apply cleanly because the getLatestRelease response structure differs on this branch. Co-Authored-By: Claude Opus 4.8 --- .../codegen/cmd/featuregate-test-analyzer.go | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/tools/codegen/cmd/featuregate-test-analyzer.go b/tools/codegen/cmd/featuregate-test-analyzer.go index 725c0057b77..923e863eb67 100644 --- a/tools/codegen/cmd/featuregate-test-analyzer.go +++ b/tools/codegen/cmd/featuregate-test-analyzer.go @@ -789,6 +789,38 @@ func filterVariants(featureGate string, variantsList ...[]JobVariant) []JobVaria return filteredVariants } +// flexibleDateTime wraps time.Time so that JSON values can be decoded from +// either an RFC 3339 timestamp (e.g. "2018-07-11T00:00:00Z") or a bare +// date-only string (e.g. "2018-07-11"). The Sippy /api/releases endpoint +// switched the development_start and ga fields from RFC 3339 timestamps to +// bare dates; supporting both keeps this tool compatible with old and new +// responses. +type flexibleDateTime struct { + time.Time +} + +// UnmarshalJSON decodes the JSON value into the wrapped time.Time, trying the +// RFC 3339 layout first and falling back to the date-only layout ("2006-01-02"). +func (f *flexibleDateTime) UnmarshalJSON(data []byte) error { + s := strings.Trim(string(data), `"`) + if s == "" || s == "null" { + return nil + } + + if t, err := time.Parse(time.RFC3339, s); err == nil { + f.Time = t + return nil + } + + t, err := time.Parse("2006-01-02", s) + if err != nil { + return fmt.Errorf("cannot parse %q as RFC 3339 or date-only (2006-01-02): %w", s, err) + } + + f.Time = t + return nil +} + // getLatestRelease returns the latest release from Sippy. func getLatestRelease() (string, error) { releaseAPI := "https://sippy.dptools.openshift.org/api/releases" @@ -810,8 +842,8 @@ func getLatestRelease() (string, error) { var result struct { Releases []string `json:"releases"` Dates map[string]struct { - GA *time.Time `json:"ga,omitempty"` - DevelopmentStart *time.Time `json:"development_start,omitempty"` + GA *flexibleDateTime `json:"ga,omitempty"` + DevelopmentStart *flexibleDateTime `json:"development_start,omitempty"` } `json:"dates"` } err = json.Unmarshal(body, &result) @@ -825,7 +857,7 @@ func getLatestRelease() (string, error) { for _, release := range result.Releases { if dates, ok := result.Dates[release]; ok { - if dates.DevelopmentStart != nil && !dates.DevelopmentStart.IsZero() && time.Now().After(*dates.DevelopmentStart) { + if dates.DevelopmentStart != nil && !dates.DevelopmentStart.Time.IsZero() && time.Now().After(dates.DevelopmentStart.Time) { return release, nil } }