Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --update flag to schedule-builder #3544

Merged
Merged
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
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
Loading