Skip to content

Commit

Permalink
Merge pull request #3544 from saschagrunert/schedule-builder-update
Browse files Browse the repository at this point in the history
Add `--update` flag to `schedule-builder`
  • Loading branch information
k8s-ci-robot authored Apr 3, 2024
2 parents da6b0dc + e12f3c0 commit 2373431
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 41 deletions.
101 changes: 93 additions & 8 deletions cmd/schedule-builder/cmd/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@ import (
"bytes"
"embed"
"fmt"
"os"
"strings"
"text/template"
"time"

"github.com/olekukonko/tablewriter"
"github.com/sirupsen/logrus"
"sigs.k8s.io/release-utils/util"
"sigs.k8s.io/yaml"
)

//go:embed templates/*.tmpl
var tpls embed.FS

// runs with `--type=patch` to return the patch schedule
func parseSchedule(patchSchedule PatchSchedule) string {
func parsePatchSchedule(patchSchedule PatchSchedule) string {
output := []string{}
output = append(output, "### Timeline\n")
for _, releaseSchedule := range patchSchedule.Schedules {
Expand Down Expand Up @@ -65,8 +69,6 @@ func parseSchedule(patchSchedule PatchSchedule) string {
scheduleOut := strings.Join(output, "\n")

logrus.Info("Schedule parsed")
println(scheduleOut)

return scheduleOut
}

Expand Down Expand Up @@ -108,15 +110,13 @@ func parseReleaseSchedule(releaseSchedule ReleaseSchedule) string {
relSched.TimelineOutput = tableString.String()
}

scheduleOut := ProcessFile("templates/rel-schedule.tmpl", relSched)
scheduleOut := processFile("templates/rel-schedule.tmpl", relSched)

logrus.Info("Release Schedule parsed")
println(scheduleOut)

return scheduleOut
}

func patchReleaseInPreviousList(a string, previousPatches []PatchRelease) bool {
func patchReleaseInPreviousList(a string, previousPatches []*PatchRelease) bool {
for _, b := range previousPatches {
if b.Release == a {
return true
Expand All @@ -141,10 +141,95 @@ func process(t *template.Template, vars interface{}) string {
return tmplBytes.String()
}

func ProcessFile(fileName string, vars interface{}) string {
func processFile(fileName string, vars interface{}) string {
tmpl, err := template.ParseFS(tpls, fileName)
if err != nil {
panic(err)
}
return process(tmpl, vars)
}

func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, filePath string) error {
const refDate = "2006-01-02"

for _, schedule := range schedule.Schedules {
for {
eolDate, err := time.Parse(refDate, schedule.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)
break
}

targetDate, err := time.Parse(refDate, schedule.Next.TargetDate)
if err != nil {
return fmt.Errorf("parse target date: %w", err)
}

if targetDate.After(refTime) {
break
}

// Copy the release to the previousPatches section
schedule.PreviousPatches = append([]*PatchRelease{schedule.Next}, schedule.PreviousPatches...)

// Create a new next release
nextReleaseVersion, err := util.TagStringToSemver(schedule.Next.Release)
if err != nil {
return fmt.Errorf("parse semver version: %w", err)
}
if err := nextReleaseVersion.IncrementPatch(); err != nil {
return fmt.Errorf("increment patch version: %w", err)
}

cherryPickDeadline, err := time.Parse(refDate, schedule.Next.CherryPickDeadline)
if err != nil {
return fmt.Errorf("parse cherry pick deadline: %w", err)
}
cherryPickDeadlinePlusOneMonth := cherryPickDeadline.AddDate(0, 1, 0)
cherryPickDay := firstFriday(cherryPickDeadlinePlusOneMonth)
newCherryPickDeadline := time.Date(cherryPickDeadlinePlusOneMonth.Year(), cherryPickDeadlinePlusOneMonth.Month(), cherryPickDay, 0, 0, 0, 0, time.UTC)

targetDatePlusOneMonth := targetDate.AddDate(0, 1, 0)
targetDateDay := secondTuesday(targetDatePlusOneMonth)
newTargetDate := time.Date(targetDatePlusOneMonth.Year(), targetDatePlusOneMonth.Month(), targetDateDay, 0, 0, 0, 0, time.UTC)

schedule.Next = &PatchRelease{
Release: nextReleaseVersion.String(),
CherryPickDeadline: newCherryPickDeadline.Format(refDate),
TargetDate: newTargetDate.Format(refDate),
}

logrus.Infof("Adding release schedule: %+v", schedule.Next)
}
}

yamlBytes, err := yaml.Marshal(schedule)
if err != nil {
return fmt.Errorf("marshal schedule YAML: %w", err)
}

//nolint:gocritic,gosec
if err := os.WriteFile(filePath, yamlBytes, 0o644); err != nil {
return fmt.Errorf("write schedule YAML: %w", err)
}

logrus.Infof("Wrote schedule YAML to: %v", filePath)
return nil
}

func secondTuesday(t time.Time) int {
return firstMonday(t) + 8
}

func firstFriday(t time.Time) int {
return firstMonday(t) + 4
}

func firstMonday(from time.Time) int {
t := time.Date(from.Year(), from.Month(), 1, 0, 0, 0, 0, time.UTC)
return (8-int(t.Weekday()))%7 + 1
}
97 changes: 91 additions & 6 deletions cmd/schedule-builder/cmd/markdown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ package cmd

import (
"fmt"
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/yaml"
)

const expectedPatchSchedule = `### Timeline
Expand Down Expand Up @@ -121,15 +125,15 @@ Please refer to the [release phases document](../release_phases.md).
[release phases document]: ../release_phases.md
`

func TestParseSchedule(t *testing.T) {
func TestParsePatchSchedule(t *testing.T) {
testcases := []struct {
name string
schedule PatchSchedule
}{
{
name: "next patch is not in previous patch list",
schedule: PatchSchedule{
Schedules: []Schedule{
Schedules: []*Schedule{
{
Release: "X.Y",
Next: &PatchRelease{
Expand All @@ -139,7 +143,7 @@ func TestParseSchedule(t *testing.T) {
},
EndOfLifeDate: "NOW",
MaintenanceModeStartDate: "THEN",
PreviousPatches: []PatchRelease{
PreviousPatches: []*PatchRelease{
{
Release: "X.Y.XXX",
CherryPickDeadline: "2020-05-15",
Expand All @@ -159,7 +163,7 @@ func TestParseSchedule(t *testing.T) {
{
name: "next patch is in previous patch list",
schedule: PatchSchedule{
Schedules: []Schedule{
Schedules: []*Schedule{
{
Release: "X.Y",
Next: &PatchRelease{
Expand All @@ -169,7 +173,7 @@ func TestParseSchedule(t *testing.T) {
},
EndOfLifeDate: "NOW",
MaintenanceModeStartDate: "THEN",
PreviousPatches: []PatchRelease{
PreviousPatches: []*PatchRelease{
{
Release: "X.Y.ZZZ",
CherryPickDeadline: "2020-06-12",
Expand All @@ -195,7 +199,7 @@ func TestParseSchedule(t *testing.T) {

for _, tc := range testcases {
fmt.Printf("Test case: %s\n", tc.name)
out := parseSchedule(tc.schedule)
out := parsePatchSchedule(tc.schedule)
require.Equal(t, out, expectedPatchSchedule)
}
}
Expand Down Expand Up @@ -319,3 +323,84 @@ func TestParseReleaseSchedule(t *testing.T) {
require.Equal(t, out, expectedReleaseSchedule)
}
}

func TestUpdatePatchSchedule(t *testing.T) {
for _, tc := range []struct {
name string
refTime time.Time
givenSchedule, expectedSchedule PatchSchedule
}{
{
name: "succeed to update the schedule",
refTime: time.Date(2024, 4, 3, 0, 0, 0, 0, time.UTC),
givenSchedule: PatchSchedule{
Schedules: []*Schedule{
{ // Needs multiple updates
Release: "1.30",
Next: &PatchRelease{
Release: "1.30.1",
CherryPickDeadline: "2024-01-05",
TargetDate: "2024-01-09",
},
EndOfLifeDate: "2025-01-01",
MaintenanceModeStartDate: "2024-12-01",
},
{ // EOL
Release: "1.20",
EndOfLifeDate: "2023-01-01",
},
},
},
expectedSchedule: PatchSchedule{
Schedules: []*Schedule{
{
Release: "1.30",
Next: &PatchRelease{
Release: "1.30.4",
CherryPickDeadline: "2024-04-05",
TargetDate: "2024-04-09",
},
EndOfLifeDate: "2025-01-01",
MaintenanceModeStartDate: "2024-12-01",
PreviousPatches: []*PatchRelease{
{
Release: "1.30.3",
CherryPickDeadline: "2024-03-08",
TargetDate: "2024-03-12",
},
{
Release: "1.30.2",
CherryPickDeadline: "2024-02-09",
TargetDate: "2024-02-13",
},
{
Release: "1.30.1",
CherryPickDeadline: "2024-01-05",
TargetDate: "2024-01-09",
},
},
},
{
Release: "1.20",
EndOfLifeDate: "2023-01-01",
},
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
file, err := os.CreateTemp("", "schedule-")
require.NoError(t, err)
require.NoError(t, file.Close())

require.NoError(t, updatePatchSchedule(tc.refTime, tc.givenSchedule, file.Name()))

yamlBytes, err := os.ReadFile(file.Name())
require.NoError(t, err)
res := PatchSchedule{}
require.NoError(t, yaml.UnmarshalStrict(yamlBytes, &res))

assert.Equal(t, tc.expectedSchedule, res)
})
}
}
28 changes: 14 additions & 14 deletions cmd/schedule-builder/cmd/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,27 @@ limitations under the License.

package cmd

// PatchSchedule main struct to hold the schedules
// PatchSchedule main struct to hold the schedules.
type PatchSchedule struct {
Schedules []Schedule `yaml:"schedules"`
Schedules []*Schedule `json:"schedules,omitempty" yaml:"schedules,omitempty"`
}

// PatchRelease struct to define the patch schedules
// PatchRelease struct to define the patch schedules.
type PatchRelease struct {
Release string `yaml:"release"`
CherryPickDeadline string `yaml:"cherryPickDeadline"`
TargetDate string `yaml:"targetDate"`
Note string `yaml:"note"`
Release string `json:"release,omitempty" yaml:"release,omitempty"`
CherryPickDeadline string `json:"cherryPickDeadline,omitempty" yaml:"cherryPickDeadline,omitempty"`
TargetDate string `json:"targetDate,omitempty" yaml:"targetDate,omitempty"`
Note string `json:"note,omitempty" yaml:"note,omitempty"`
}

// Schedule struct to define the release schedule for a specific version
// Schedule struct to define the release schedule for a specific version.
type Schedule struct {
Release string `yaml:"release"`
ReleaseDate string `yaml:"releaseDate"`
Next *PatchRelease `yaml:"next"`
EndOfLifeDate string `yaml:"endOfLifeDate"`
MaintenanceModeStartDate string `yaml:"maintenanceModeStartDate"`
PreviousPatches []PatchRelease `yaml:"previousPatches"`
Release string `json:"release,omitempty" yaml:"release,omitempty"`
ReleaseDate string `json:"releaseDate,omitempty" yaml:"releaseDate,omitempty"`
Next *PatchRelease `json:"next,omitempty" yaml:"next,omitempty"`
EndOfLifeDate string `json:"endOfLifeDate,omitempty" yaml:"endOfLifeDate,omitempty"`
MaintenanceModeStartDate string `json:"maintenanceModeStartDate,omitempty" yaml:"maintenanceModeStartDate,omitempty"`
PreviousPatches []*PatchRelease `json:"previousPatches,omitempty" yaml:"previousPatches,omitempty"`
}

type ReleaseSchedule struct {
Expand Down
Loading

0 comments on commit 2373431

Please sign in to comment.