From 4917b8ce4139fa7a17ac0ed6f2306dfc33d4ce2f Mon Sep 17 00:00:00 2001 From: Sascha Grunert Date: Thu, 4 Apr 2024 14:55:44 +0200 Subject: [PATCH] Add `-e/--eol-config-path` support to `schedule-builder` It's not possible to specify an optional EOL config path. This file will be updated when a patch release goes end of life when specified. Signed-off-by: Sascha Grunert --- cmd/schedule-builder/cmd/markdown.go | 61 +++++++++++++++++++---- cmd/schedule-builder/cmd/markdown_test.go | 44 ++++++++++++---- cmd/schedule-builder/cmd/model.go | 13 +++++ cmd/schedule-builder/cmd/root.go | 58 +++++++++++++++------ 4 files changed, 140 insertions(+), 36 deletions(-) diff --git a/cmd/schedule-builder/cmd/markdown.go b/cmd/schedule-builder/cmd/markdown.go index d50ff8c1b85..15e8e356de6 100644 --- a/cmd/schedule-builder/cmd/markdown.go +++ b/cmd/schedule-builder/cmd/markdown.go @@ -167,22 +167,34 @@ func processFile(fileName string, vars interface{}) string { return process(tmpl, vars) } -func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, filePath string) error { +func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, eolBranches EolBranches, filePath, eolFilePath string) error { const refDate = "2006-01-02" - for _, schedule := range schedule.Schedules { + removeSchedules := []int{} + for i, sched := range schedule.Schedules { for { - eolDate, err := time.Parse(refDate, schedule.EndOfLifeDate) + eolDate, err := time.Parse(refDate, sched.EndOfLifeDate) if err != nil { return fmt.Errorf("parse end of life date: %w", err) } if refTime.After(eolDate) { - logrus.Infof("Skipping end of life release: %s", schedule.Release) + if eolFilePath == "" { + logrus.Infof("Skipping end of life release: %s", sched.Release) + break + } + + logrus.Infof("Moving %s to end of life", sched.Release) + eolBranches.Branches = append([]*EolBranch{{ + Release: sched.Release, + FinalPatchRelease: sched.Next.Release, + EndOfLifeDate: sched.Next.TargetDate, + }}, eolBranches.Branches...) + removeSchedules = append(removeSchedules, i) break } - targetDate, err := time.Parse(refDate, schedule.Next.TargetDate) + targetDate, err := time.Parse(refDate, sched.Next.TargetDate) if err != nil { return fmt.Errorf("parse target date: %w", err) } @@ -192,10 +204,10 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, filePath str } // Copy the release to the previousPatches section - schedule.PreviousPatches = append([]*PatchRelease{schedule.Next}, schedule.PreviousPatches...) + sched.PreviousPatches = append([]*PatchRelease{sched.Next}, sched.PreviousPatches...) // Create a new next release - nextReleaseVersion, err := util.TagStringToSemver(schedule.Next.Release) + nextReleaseVersion, err := util.TagStringToSemver(sched.Next.Release) if err != nil { return fmt.Errorf("parse semver version: %w", err) } @@ -203,7 +215,7 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, filePath str return fmt.Errorf("increment patch version: %w", err) } - cherryPickDeadline, err := time.Parse(refDate, schedule.Next.CherryPickDeadline) + cherryPickDeadline, err := time.Parse(refDate, sched.Next.CherryPickDeadline) if err != nil { return fmt.Errorf("parse cherry pick deadline: %w", err) } @@ -215,16 +227,31 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, filePath str targetDateDay := secondTuesday(targetDatePlusOneMonth) newTargetDate := time.Date(targetDatePlusOneMonth.Year(), targetDatePlusOneMonth.Month(), targetDateDay, 0, 0, 0, 0, time.UTC) - schedule.Next = &PatchRelease{ + sched.Next = &PatchRelease{ Release: nextReleaseVersion.String(), CherryPickDeadline: newCherryPickDeadline.Format(refDate), TargetDate: newTargetDate.Format(refDate), } - logrus.Infof("Adding release schedule: %+v", schedule.Next) + logrus.Infof("Adding release schedule: %+v", sched.Next) } } + newSchedules := []*Schedule{} + for i, sched := range schedule.Schedules { + appendItem := true + for _, k := range removeSchedules { + if i == k { + appendItem = false + break + } + } + if appendItem { + newSchedules = append(newSchedules, sched) + } + } + schedule.Schedules = newSchedules + newUpcomingReleases := []*PatchRelease{} latestDate := refTime for _, upcomingRelease := range schedule.UpcomingReleases { @@ -275,6 +302,20 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, filePath str return fmt.Errorf("write schedule YAML: %w", err) } + if eolFilePath != "" { + logrus.Infof("Writing end of life branches: %s", eolFilePath) + + yamlBytes, err := yaml.Marshal(eolBranches) + if err != nil { + return fmt.Errorf("marshal end of life YAML: %w", err) + } + + //nolint:gocritic,gosec + if err := os.WriteFile(eolFilePath, yamlBytes, 0o644); err != nil { + return fmt.Errorf("write end of life YAML: %w", err) + } + } + logrus.Infof("Wrote schedule YAML to: %v", filePath) return nil } diff --git a/cmd/schedule-builder/cmd/markdown_test.go b/cmd/schedule-builder/cmd/markdown_test.go index bf30df6fd4b..ea60d5578b6 100644 --- a/cmd/schedule-builder/cmd/markdown_test.go +++ b/cmd/schedule-builder/cmd/markdown_test.go @@ -349,6 +349,7 @@ func TestUpdatePatchSchedule(t *testing.T) { name string refTime time.Time givenSchedule, expectedSchedule PatchSchedule + expectedEolBranches EolBranches }{ { name: "succeed to update the schedule", @@ -368,6 +369,11 @@ func TestUpdatePatchSchedule(t *testing.T) { { // EOL Release: "1.20", EndOfLifeDate: "2023-01-01", + Next: &PatchRelease{ + Release: "1.20.10", + CherryPickDeadline: "2023-12-08", + TargetDate: "2023-12-12", + }, }, }, UpcomingReleases: []*PatchRelease{ @@ -417,10 +423,6 @@ func TestUpdatePatchSchedule(t *testing.T) { }, }, }, - { - Release: "1.20", - EndOfLifeDate: "2023-01-01", - }, }, UpcomingReleases: []*PatchRelease{ { @@ -440,21 +442,41 @@ func TestUpdatePatchSchedule(t *testing.T) { }, }, }, + expectedEolBranches: EolBranches{ + Branches: []*EolBranch{ + { + Release: "1.20", + FinalPatchRelease: "1.20.10", + EndOfLifeDate: "2023-12-12", + }, + }, + }, }, } { t.Run(tc.name, func(t *testing.T) { - file, err := os.CreateTemp("", "schedule-") + scheduleFile, err := os.CreateTemp("", "schedule-") + require.NoError(t, err) + require.NoError(t, scheduleFile.Close()) + + eolFile, err := os.CreateTemp("", "eol-") + require.NoError(t, err) + require.NoError(t, eolFile.Close()) + + require.NoError(t, updatePatchSchedule(tc.refTime, tc.givenSchedule, EolBranches{}, scheduleFile.Name(), eolFile.Name())) + + scheduleYamlBytes, err := os.ReadFile(scheduleFile.Name()) require.NoError(t, err) - require.NoError(t, file.Close()) + patchRes := PatchSchedule{} + require.NoError(t, yaml.UnmarshalStrict(scheduleYamlBytes, &patchRes)) - require.NoError(t, updatePatchSchedule(tc.refTime, tc.givenSchedule, file.Name())) + assert.Equal(t, tc.expectedSchedule, patchRes) - yamlBytes, err := os.ReadFile(file.Name()) + eolYamlBytes, err := os.ReadFile(eolFile.Name()) require.NoError(t, err) - res := PatchSchedule{} - require.NoError(t, yaml.UnmarshalStrict(yamlBytes, &res)) + eolRes := EolBranches{} + require.NoError(t, yaml.UnmarshalStrict(eolYamlBytes, &eolRes)) - assert.Equal(t, tc.expectedSchedule, res) + assert.Equal(t, tc.expectedEolBranches, eolRes) }) } } diff --git a/cmd/schedule-builder/cmd/model.go b/cmd/schedule-builder/cmd/model.go index f0bd12ef309..894862fe2fe 100644 --- a/cmd/schedule-builder/cmd/model.go +++ b/cmd/schedule-builder/cmd/model.go @@ -40,6 +40,19 @@ type Schedule struct { PreviousPatches []*PatchRelease `json:"previousPatches,omitempty" yaml:"previousPatches,omitempty"` } +// EolBranches is main struct to hold the end of life branches. +type EolBranches struct { + Branches []*EolBranch `json:"branches,omitempty" yaml:"branches,omitempty"` +} + +// EolBranch struct to define the end of life release branches. +type EolBranch struct { + Release string `json:"release,omitempty" yaml:"release,omitempty"` + FinalPatchRelease string `json:"finalPatchRelease,omitempty" yaml:"finalPatchRelease,omitempty"` + EndOfLifeDate string `json:"endOfLifeDate,omitempty" yaml:"endOfLifeDate,omitempty"` + Note string `json:"note,omitempty" yaml:"note,omitempty"` +} + type ReleaseSchedule struct { Releases []Release `yaml:"releases"` } diff --git a/cmd/schedule-builder/cmd/root.go b/cmd/schedule-builder/cmd/root.go index 7898b65b5b2..c0ce4c5d6c1 100644 --- a/cmd/schedule-builder/cmd/root.go +++ b/cmd/schedule-builder/cmd/root.go @@ -43,24 +43,26 @@ var rootCmd = &cobra.Command{ } type options struct { - configPath string - outputFile string - logLevel string - typeFile string - update bool - version bool + configPath string + eolConfigPath string + outputFile string + logLevel string + typeFile string + update bool + version bool } var opts = &options{} const ( - configPathFlag = "config-path" - outputFileFlag = "output-file" - typeFlag = "type" - updateFlag = "update" - versionFlag = "version" - typePatch = "patch" - typeRelease = "release" + configPathFlag = "config-path" + eolConfigPathFlag = "eol-config-path" + outputFileFlag = "output-file" + typeFlag = "type" + updateFlag = "update" + versionFlag = "version" + typePatch = "patch" + typeRelease = "release" ) // Execute adds all child commands to the root command and sets flags appropriately. @@ -80,6 +82,14 @@ func init() { "path where can find the schedule.yaml file", ) + rootCmd.PersistentFlags().StringVarP( + &opts.eolConfigPath, + eolConfigPathFlag, + "e", + "", + "path where can find the eol.yaml file for updating end of life releases", + ) + rootCmd.PersistentFlags().StringVarP( &opts.outputFile, outputFileFlag, @@ -145,6 +155,7 @@ func run(opts *options) error { var ( patchSchedule PatchSchedule releaseSchedule ReleaseSchedule + eolBranches EolBranches scheduleOut string ) @@ -153,12 +164,29 @@ func run(opts *options) error { switch opts.typeFile { case typePatch: if err := yaml.UnmarshalStrict(data, &patchSchedule); err != nil { - return fmt.Errorf("failed to decode the file: %w", err) + return fmt.Errorf("failed to decode patch schedule: %w", err) + } + + if opts.eolConfigPath != "" { + data, err := os.ReadFile(opts.eolConfigPath) + if err != nil { + return fmt.Errorf("failed to read end of life config path: %w", err) + } + + if err := yaml.UnmarshalStrict(data, &eolBranches); err != nil { + return fmt.Errorf("failed to decode end of life branches: %w", err) + } } if opts.update { logrus.Info("Updating schedule") - if err := updatePatchSchedule(time.Now(), patchSchedule, opts.configPath); err != nil { + if err := updatePatchSchedule( + time.Now(), + patchSchedule, + eolBranches, + opts.configPath, + opts.eolConfigPath, + ); err != nil { return fmt.Errorf("update patch schedule: %w", err) } } else {