From bde7c46d45c4792d20e81c9470a3b2dd9db4226e Mon Sep 17 00:00:00 2001
From: cpanato
Date: Thu, 30 May 2024 10:49:11 +0200
Subject: [PATCH 1/3] add aws lambda function to send patch cherry pick
notification
Signed-off-by: cpanato
---
cmd/patch-release-notification/README.md | 4 +
cmd/patch-release-notification/main.go | 318 ++++++++++++++++++
.../templates/email.tmpl | 32 ++
cmd/schedule-builder/cmd/markdown.go | 26 +-
cmd/schedule-builder/cmd/markdown_test.go | 66 ++--
cmd/schedule-builder/cmd/root.go | 7 +-
cmd/schedule-builder/{cmd => model}/model.go | 2 +-
go.mod | 7 +-
go.sum | 8 +
iac/patch-release-notification/README.md | 8 +
iac/patch-release-notification/main.tf | 110 ++++++
iac/patch-release-notification/outputs.tf | 3 +
.../terraform.tfvars | 7 +
iac/patch-release-notification/variables.tf | 35 ++
iac/patch-release-notification/versions.tf | 21 ++
15 files changed, 605 insertions(+), 49 deletions(-)
create mode 100644 cmd/patch-release-notification/README.md
create mode 100644 cmd/patch-release-notification/main.go
create mode 100644 cmd/patch-release-notification/templates/email.tmpl
rename cmd/schedule-builder/{cmd => model}/model.go (99%)
create mode 100644 iac/patch-release-notification/README.md
create mode 100644 iac/patch-release-notification/main.tf
create mode 100644 iac/patch-release-notification/outputs.tf
create mode 100644 iac/patch-release-notification/terraform.tfvars
create mode 100644 iac/patch-release-notification/variables.tf
create mode 100644 iac/patch-release-notification/versions.tf
diff --git a/cmd/patch-release-notification/README.md b/cmd/patch-release-notification/README.md
new file mode 100644
index 00000000000..0da11dbb52c
--- /dev/null
+++ b/cmd/patch-release-notification/README.md
@@ -0,0 +1,4 @@
+# Patch Release Notification
+
+This simple tool has the objective to send an notification email when we are closer to
+the patch release cycle to let people know that the cherry pick deadline is approaching.
diff --git a/cmd/patch-release-notification/main.go b/cmd/patch-release-notification/main.go
new file mode 100644
index 00000000000..77555faea24
--- /dev/null
+++ b/cmd/patch-release-notification/main.go
@@ -0,0 +1,318 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+ "bytes"
+ "context"
+ "embed"
+ "fmt"
+ "io"
+ "log"
+ "math"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+ "text/template"
+ "time"
+
+ "gomodules.xyz/envconfig"
+ "gopkg.in/gomail.v2"
+ "gopkg.in/yaml.v3"
+
+ "github.com/aws/aws-lambda-go/events"
+ "github.com/aws/aws-lambda-go/lambda"
+ "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/aws-sdk-go/aws/session"
+ "github.com/aws/aws-sdk-go/service/ses"
+ "github.com/sirupsen/logrus"
+
+ "k8s.io/release/cmd/schedule-builder/model"
+)
+
+//go:embed templates/email.tmpl
+var tpls embed.FS
+
+type Config struct {
+ FromEmail string `envconfig:"FROM_EMAIL" required:"true"`
+ ToEmail string `envconfig:"TO_EMAIL" required:"true"`
+ SchedulePath string `envconfig:"SCHEDULE_PATH" required:"true"`
+ DaysToAlert int `envconfig:"DAYS_TO_ALERT" required:"true"`
+
+ NoMock bool `default:"false" envconfig:"NO_MOCK" required:"true"`
+
+ AWSRegion string `envconfig:"AWS_REGION" required:"true"`
+}
+
+type Options struct {
+ AWSSess *session.Session
+ Config *Config
+ Context context.Context
+}
+
+const (
+ layout = "2006-01-02"
+)
+
+type Template struct {
+ Releases []TemplateRelease
+}
+
+type TemplateRelease struct {
+ Release string
+ CherryPickDeadline string
+}
+
+func main() {
+ lambda.Start(handler)
+}
+
+func getConfig() (*Config, error) {
+ var c Config
+ err := envconfig.Process("", &c)
+ if err != nil {
+ return nil, err
+ }
+ return &c, nil
+}
+
+func New(ctx context.Context) (*Options, error) {
+ config, err := getConfig()
+ if err != nil {
+ return nil, fmt.Errorf("failed to get config: %w", err)
+ }
+
+ // create new AWS session
+ sess, err := session.NewSession(&aws.Config{
+ Region: aws.String(config.AWSRegion),
+ })
+ if err != nil {
+ log.Println("Error occurred while creating aws session", err)
+ return nil, err
+ }
+
+ return &Options{
+ AWSSess: sess,
+ Config: config,
+ Context: ctx,
+ }, nil
+}
+
+func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { //nolint: gocritic
+ o, err := New(ctx)
+ if err != nil {
+ return events.APIGatewayProxyResponse{
+ Body: `{"status": "nok"}`,
+ StatusCode: http.StatusInternalServerError,
+ }, err
+ }
+
+ data, err := loadFileOrURL(o.Config.SchedulePath)
+ if err != nil {
+ return events.APIGatewayProxyResponse{
+ Body: `{"status": "nok"}`,
+ StatusCode: http.StatusInternalServerError,
+ }, fmt.Errorf("failed to read the file: %w", err)
+ }
+
+ patchSchedule := &model.PatchSchedule{}
+
+ logrus.Info("Parsing the schedule...")
+
+ if err := yaml.Unmarshal(data, &patchSchedule); err != nil {
+ return events.APIGatewayProxyResponse{
+ Body: `{"status": "nok"}`,
+ StatusCode: http.StatusInternalServerError,
+ }, fmt.Errorf("failed to decode the file: %w", err)
+ }
+
+ output := &Template{}
+
+ shouldSendEmail := false
+
+ for _, patch := range patchSchedule.Schedules {
+ t, err := time.Parse(layout, patch.Next.CherryPickDeadline)
+ if err != nil {
+ return events.APIGatewayProxyResponse{
+ Body: `{"status": "nok"}`,
+ StatusCode: http.StatusInternalServerError,
+ }, fmt.Errorf("parsing schedule time: %w", err)
+ }
+
+ currentTime := time.Now().UTC()
+ days := t.Sub(currentTime).Hours() / 24
+ intDay, _ := math.Modf(days)
+ logrus.Infof("cherry pick deadline: %d, days to alert: %d", int(intDay), o.Config.DaysToAlert)
+ if int(intDay) == o.Config.DaysToAlert {
+ output.Releases = append(output.Releases, TemplateRelease{
+ Release: patch.Release,
+ CherryPickDeadline: patch.Next.CherryPickDeadline,
+ })
+ shouldSendEmail = true
+ }
+ }
+
+ tmpl, err := template.ParseFS(tpls, "templates/email.tmpl")
+ if err != nil {
+ return events.APIGatewayProxyResponse{
+ Body: `{"status": "nok"}`,
+ StatusCode: http.StatusInternalServerError,
+ }, fmt.Errorf("parsing template: %w", err)
+ }
+
+ var tmplBytes bytes.Buffer
+ err = tmpl.Execute(&tmplBytes, output)
+ if err != nil {
+ return events.APIGatewayProxyResponse{
+ Body: `{"status": "nok"}`,
+ StatusCode: http.StatusInternalServerError,
+ }, fmt.Errorf("parsing values to the template: %w", err)
+ }
+
+ if !shouldSendEmail {
+ logrus.Info("No email is needed to send")
+ return events.APIGatewayProxyResponse{
+ Body: `{"status": "ok"}`,
+ StatusCode: http.StatusOK,
+ }, nil
+ }
+
+ logrus.Info("Sending mail")
+ subject := "[Please Read] Patch Releases cherry-pick deadline"
+ fromEmail := o.Config.FromEmail
+
+ recipient := Recipient{
+ toEmails: []string{o.Config.ToEmail},
+ }
+
+ if !o.Config.NoMock {
+ logrus.Info("This is a mock only, will print out the email before sending to a test mailing list")
+ fmt.Println(tmplBytes.String())
+ // if is a mock we send the email to ourselves to test
+ recipient.toEmails = []string{o.Config.FromEmail}
+ }
+
+ err = o.SendEmailRawSES(tmplBytes.String(), subject, fromEmail, recipient)
+ if err != nil {
+ return events.APIGatewayProxyResponse{
+ Body: `{"status": "nok"}`,
+ StatusCode: http.StatusInternalServerError,
+ }, fmt.Errorf("parsing values to the template: %w", err)
+ }
+
+ return events.APIGatewayProxyResponse{
+ Body: `{"status": "ok"}`,
+ StatusCode: 200,
+ }, nil
+}
+
+// Recipient struct to hold email IDs
+type Recipient struct {
+ toEmails []string
+ ccEmails []string
+ bccEmails []string
+}
+
+// SendEmailSES sends email to specified email IDs
+func (o *Options) SendEmailRawSES(messageBody, subject, fromEmail string, recipient Recipient) error {
+ // create raw message
+ msg := gomail.NewMessage()
+
+ // set to section
+ recipients := make([]*string, 0, len(recipient.toEmails))
+ for _, r := range recipient.toEmails {
+ recipient := r
+ recipients = append(recipients, &recipient)
+ }
+
+ // Set to emails
+ msg.SetHeader("To", recipient.toEmails...)
+
+ // cc mails mentioned
+ if len(recipient.ccEmails) != 0 {
+ // Need to add cc mail IDs also in recipient list
+ for _, r := range recipient.ccEmails {
+ recipient := r
+ recipients = append(recipients, &recipient)
+ }
+ msg.SetHeader("cc", recipient.ccEmails...)
+ }
+
+ // bcc mails mentioned
+ if len(recipient.bccEmails) != 0 {
+ // Need to add bcc mail IDs also in recipient list
+ for _, r := range recipient.bccEmails {
+ recipient := r
+ recipients = append(recipients, &recipient)
+ }
+ msg.SetHeader("bcc", recipient.bccEmails...)
+ }
+
+ // create an SES session.
+ svc := ses.New(o.AWSSess)
+
+ msg.SetAddressHeader("From", fromEmail, "Release Managers")
+ msg.SetHeader("To", recipient.toEmails...)
+ msg.SetHeader("Subject", subject)
+ msg.SetBody("text/html", messageBody)
+
+ // create a new buffer to add raw data
+ var emailRaw bytes.Buffer
+ _, err := msg.WriteTo(&emailRaw)
+ if err != nil {
+ log.Printf("failed to write mail: %v\n", err)
+ return err
+ }
+
+ // create new raw message
+ message := ses.RawMessage{Data: emailRaw.Bytes()}
+
+ input := &ses.SendRawEmailInput{Source: &fromEmail, Destinations: recipients, RawMessage: &message}
+
+ // send raw email
+ _, err = svc.SendRawEmail(input)
+ if err != nil {
+ log.Println("Error sending mail - ", err)
+ return err
+ }
+
+ log.Println("Email sent successfully to: ", recipient.toEmails)
+ return nil
+}
+
+func loadFileOrURL(fileRef string) ([]byte, error) {
+ var raw []byte
+ var err error
+ if strings.HasPrefix(fileRef, "http://") || strings.HasPrefix(fileRef, "https://") {
+ resp, err := http.Get(fileRef) //nolint:gosec // we are not using user input we set via env var
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ raw, err = io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ raw, err = os.ReadFile(filepath.Clean(fileRef))
+ if err != nil {
+ return nil, err
+ }
+ }
+ return raw, nil
+}
diff --git a/cmd/patch-release-notification/templates/email.tmpl b/cmd/patch-release-notification/templates/email.tmpl
new file mode 100644
index 00000000000..6fbdd7704e9
--- /dev/null
+++ b/cmd/patch-release-notification/templates/email.tmpl
@@ -0,0 +1,32 @@
+
+
+
+Hello Kubernetes Community!
+{{range .Releases}}
+The cherry-pick deadline for the {{ .Release }} branches is {{ .CherryPickDeadline }} EOD PT.
+{{end}}
+
Here are some quick links to search for cherry-pick PRs:
+{{range .Releases}}
+ - release-{{ .Release }}: https://github.com/kubernetes/kubernetes/pulls?q=is%3Apr+is%3Aopen+base%3Arelease-{{ .Release }}+label%3Ado-not-merge%2Fcherry-pick-not-approved
+{{end}}
+
+For PRs that you intend to land for the upcoming patch sets, please
+ensure they have:
+ - a release note in the PR description
+ - /sig
+ - /kind
+ - /priority
+ - /lgtm
+ - /approve
+ - passing tests
+
+Details on the cherry-pick process can be found here:
+https://git.k8s.io/community/contributors/devel/sig-release/cherry-picks.md
+We keep general info and up-to-date timelines for patch releases here:
+https://kubernetes.io/releases/patch-releases/#upcoming-monthly-releases
+If you have any questions for the Release Managers, please feel free to
+reach out to us at #release-management (Kubernetes Slack) or release-managers@kubernetes.io
+We wish everyone a happy and safe week!
+SIG-Release Team
+
+
diff --git a/cmd/schedule-builder/cmd/markdown.go b/cmd/schedule-builder/cmd/markdown.go
index 20fc978184b..5b5712355ff 100644
--- a/cmd/schedule-builder/cmd/markdown.go
+++ b/cmd/schedule-builder/cmd/markdown.go
@@ -29,6 +29,8 @@ import (
"github.com/olekukonko/tablewriter"
"github.com/sirupsen/logrus"
+
+ "k8s.io/release/cmd/schedule-builder/model"
"sigs.k8s.io/release-utils/util"
"sigs.k8s.io/yaml"
)
@@ -37,7 +39,7 @@ import (
var tpls embed.FS
// runs with `--type=patch` to return the patch schedule
-func parsePatchSchedule(patchSchedule PatchSchedule) string {
+func parsePatchSchedule(patchSchedule model.PatchSchedule) string {
output := []string{}
if len(patchSchedule.UpcomingReleases) > 0 {
@@ -102,11 +104,11 @@ func parsePatchSchedule(patchSchedule PatchSchedule) string {
}
// runs with `--type=release` to return the release cycle schedule
-func parseReleaseSchedule(releaseSchedule ReleaseSchedule) string {
+func parseReleaseSchedule(releaseSchedule model.ReleaseSchedule) string {
type RelSched struct {
K8VersionWithDot string
K8VersionWithoutDot string
- Arr []Timeline
+ Arr []model.Timeline
TimelineOutput string
}
@@ -114,7 +116,7 @@ func parseReleaseSchedule(releaseSchedule ReleaseSchedule) string {
relSched.K8VersionWithDot = releaseSchedule.Releases[0].Version
relSched.K8VersionWithoutDot = removeDotfromVersion(releaseSchedule.Releases[0].Version)
- relSched.Arr = []Timeline{}
+ relSched.Arr = []model.Timeline{}
for _, releaseSchedule := range releaseSchedule.Releases {
for _, timeline := range releaseSchedule.Timeline {
if timeline.Tldr {
@@ -145,7 +147,7 @@ func parseReleaseSchedule(releaseSchedule ReleaseSchedule) string {
return scheduleOut
}
-func patchReleaseInPreviousList(a string, previousPatches []*PatchRelease) bool {
+func patchReleaseInPreviousList(a string, previousPatches []*model.PatchRelease) bool {
for _, b := range previousPatches {
if b.Release == a {
return true
@@ -189,7 +191,7 @@ const (
`
)
-func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, eolBranches EolBranches, filePath, eolFilePath string) error {
+func updatePatchSchedule(refTime time.Time, schedule model.PatchSchedule, eolBranches model.EolBranches, filePath, eolFilePath string) error {
removeSchedules := []int{}
for i, sched := range schedule.Schedules {
for {
@@ -210,7 +212,7 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, eolBranches
}
logrus.Infof("Moving %s to end of life", sched.Release)
- eolBranches.Branches = append([]*EolBranch{{
+ eolBranches.Branches = append([]*model.EolBranch{{
Release: sched.Release,
FinalPatchRelease: sched.Next.Release,
EndOfLifeDate: sched.Next.TargetDate,
@@ -229,7 +231,7 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, eolBranches
}
// Copy the release to the previousPatches section
- sched.PreviousPatches = append([]*PatchRelease{sched.Next}, sched.PreviousPatches...)
+ sched.PreviousPatches = append([]*model.PatchRelease{sched.Next}, sched.PreviousPatches...)
// Create a new next release
nextReleaseVersion, err := util.TagStringToSemver(sched.Next.Release)
@@ -252,7 +254,7 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, eolBranches
targetDateDay := secondTuesday(targetDatePlusOneMonth)
newTargetDate := time.Date(targetDatePlusOneMonth.Year(), targetDatePlusOneMonth.Month(), targetDateDay, 0, 0, 0, 0, time.UTC)
- sched.Next = &PatchRelease{
+ sched.Next = &model.PatchRelease{
Release: nextReleaseVersion.String(),
CherryPickDeadline: newCherryPickDeadline.Format(refDate),
TargetDate: newTargetDate.Format(refDate),
@@ -262,7 +264,7 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, eolBranches
}
}
- newSchedules := []*Schedule{}
+ newSchedules := []*model.Schedule{}
for i, sched := range schedule.Schedules {
appendItem := true
for _, k := range removeSchedules {
@@ -277,7 +279,7 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, eolBranches
}
schedule.Schedules = newSchedules
- newUpcomingReleases := []*PatchRelease{}
+ newUpcomingReleases := []*model.PatchRelease{}
latestDate := refTime
for _, upcomingRelease := range schedule.UpcomingReleases {
upcomingTargetDate, err := time.Parse(refDate, upcomingRelease.TargetDate)
@@ -308,7 +310,7 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, eolBranches
logrus.Infof("Adding new upcoming release for %s", nextTargetDate.Format(refDateMonthly))
- newUpcomingReleases = append(newUpcomingReleases, &PatchRelease{
+ newUpcomingReleases = append(newUpcomingReleases, &model.PatchRelease{
CherryPickDeadline: nextCherryPickDeadline.Format(refDate),
TargetDate: nextTargetDate.Format(refDate),
})
diff --git a/cmd/schedule-builder/cmd/markdown_test.go b/cmd/schedule-builder/cmd/markdown_test.go
index 70d4aa32543..6e3145823ac 100644
--- a/cmd/schedule-builder/cmd/markdown_test.go
+++ b/cmd/schedule-builder/cmd/markdown_test.go
@@ -25,6 +25,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/yaml"
+
+ "k8s.io/release/cmd/schedule-builder/model"
)
const expectedPatchSchedule = `### Upcoming Monthly Releases
@@ -134,22 +136,22 @@ Please refer to the [release phases document](../release_phases.md).
func TestParsePatchSchedule(t *testing.T) {
testcases := []struct {
name string
- schedule PatchSchedule
+ schedule model.PatchSchedule
}{
{
name: "next patch is not in previous patch list",
- schedule: PatchSchedule{
- Schedules: []*Schedule{
+ schedule: model.PatchSchedule{
+ Schedules: []*model.Schedule{
{
Release: "X.Y",
- Next: &PatchRelease{
+ Next: &model.PatchRelease{
Release: "X.Y.ZZZ",
CherryPickDeadline: "2020-06-12",
TargetDate: "2020-06-17",
},
EndOfLifeDate: "NOW",
MaintenanceModeStartDate: "THEN",
- PreviousPatches: []*PatchRelease{
+ PreviousPatches: []*model.PatchRelease{
{
Release: "X.Y.XXX",
CherryPickDeadline: "2020-05-15",
@@ -164,7 +166,7 @@ func TestParsePatchSchedule(t *testing.T) {
},
},
},
- UpcomingReleases: []*PatchRelease{
+ UpcomingReleases: []*model.PatchRelease{
{
CherryPickDeadline: "2020-06-12",
TargetDate: "2020-06-17",
@@ -174,18 +176,18 @@ func TestParsePatchSchedule(t *testing.T) {
},
{
name: "next patch is in previous patch list",
- schedule: PatchSchedule{
- Schedules: []*Schedule{
+ schedule: model.PatchSchedule{
+ Schedules: []*model.Schedule{
{
Release: "X.Y",
- Next: &PatchRelease{
+ Next: &model.PatchRelease{
Release: "X.Y.ZZZ",
CherryPickDeadline: "2020-06-12",
TargetDate: "2020-06-17",
},
EndOfLifeDate: "NOW",
MaintenanceModeStartDate: "THEN",
- PreviousPatches: []*PatchRelease{
+ PreviousPatches: []*model.PatchRelease{
{
Release: "X.Y.ZZZ",
CherryPickDeadline: "2020-06-12",
@@ -205,7 +207,7 @@ func TestParsePatchSchedule(t *testing.T) {
},
},
},
- UpcomingReleases: []*PatchRelease{
+ UpcomingReleases: []*model.PatchRelease{
{
CherryPickDeadline: "2020-06-12",
TargetDate: "2020-06-17",
@@ -225,15 +227,15 @@ func TestParsePatchSchedule(t *testing.T) {
func TestParseReleaseSchedule(t *testing.T) {
testcases := []struct {
name string
- schedule ReleaseSchedule
+ schedule model.ReleaseSchedule
}{
{
name: "test of release cycle of X.Y version",
- schedule: ReleaseSchedule{
- Releases: []Release{
+ schedule: model.ReleaseSchedule{
+ Releases: []model.Release{
{
Version: "X.Y",
- Timeline: []Timeline{
+ Timeline: []model.Timeline{
{
What: "Testing-A",
Who: "tester",
@@ -346,17 +348,17 @@ func TestUpdatePatchSchedule(t *testing.T) {
for _, tc := range []struct {
name string
refTime time.Time
- givenSchedule, expectedSchedule PatchSchedule
- expectedEolBranches EolBranches
+ givenSchedule, expectedSchedule model.PatchSchedule
+ expectedEolBranches model.EolBranches
}{
{
name: "succeed to update the schedule",
refTime: time.Date(2024, 4, 3, 0, 0, 0, 0, time.UTC),
- givenSchedule: PatchSchedule{
- Schedules: []*Schedule{
+ givenSchedule: model.PatchSchedule{
+ Schedules: []*model.Schedule{
{ // Needs multiple updates
Release: "1.30",
- Next: &PatchRelease{
+ Next: &model.PatchRelease{
Release: "1.30.1",
CherryPickDeadline: "2024-01-05",
TargetDate: "2024-01-09",
@@ -370,14 +372,14 @@ func TestUpdatePatchSchedule(t *testing.T) {
{ // EOL
Release: "1.20",
EndOfLifeDate: "2023-01-01",
- Next: &PatchRelease{
+ Next: &model.PatchRelease{
Release: "1.20.10",
CherryPickDeadline: "2023-12-08",
TargetDate: "2023-12-12",
},
},
},
- UpcomingReleases: []*PatchRelease{
+ UpcomingReleases: []*model.PatchRelease{
{
CherryPickDeadline: "2024-03-08",
TargetDate: "2024-03-13",
@@ -392,18 +394,18 @@ func TestUpdatePatchSchedule(t *testing.T) {
},
},
},
- expectedSchedule: PatchSchedule{
- Schedules: []*Schedule{
+ expectedSchedule: model.PatchSchedule{
+ Schedules: []*model.Schedule{
{
Release: "1.30",
- Next: &PatchRelease{
+ Next: &model.PatchRelease{
Release: "1.30.4",
CherryPickDeadline: "2024-04-05",
TargetDate: "2024-04-09",
},
EndOfLifeDate: "2025-01-01",
MaintenanceModeStartDate: "2024-12-01",
- PreviousPatches: []*PatchRelease{
+ PreviousPatches: []*model.PatchRelease{
{
Release: "1.30.3",
CherryPickDeadline: "2024-03-08",
@@ -425,7 +427,7 @@ func TestUpdatePatchSchedule(t *testing.T) {
Release: "1.29",
},
},
- UpcomingReleases: []*PatchRelease{
+ UpcomingReleases: []*model.PatchRelease{
{
CherryPickDeadline: "2024-04-12",
TargetDate: "2024-04-17",
@@ -440,8 +442,8 @@ func TestUpdatePatchSchedule(t *testing.T) {
},
},
},
- expectedEolBranches: EolBranches{
- Branches: []*EolBranch{
+ expectedEolBranches: model.EolBranches{
+ Branches: []*model.EolBranch{
{
Release: "1.20",
FinalPatchRelease: "1.20.10",
@@ -460,18 +462,18 @@ func TestUpdatePatchSchedule(t *testing.T) {
require.NoError(t, err)
require.NoError(t, eolFile.Close())
- require.NoError(t, updatePatchSchedule(tc.refTime, tc.givenSchedule, EolBranches{}, scheduleFile.Name(), eolFile.Name()))
+ require.NoError(t, updatePatchSchedule(tc.refTime, tc.givenSchedule, model.EolBranches{}, scheduleFile.Name(), eolFile.Name()))
scheduleYamlBytes, err := os.ReadFile(scheduleFile.Name())
require.NoError(t, err)
- patchRes := PatchSchedule{}
+ patchRes := model.PatchSchedule{}
require.NoError(t, yaml.UnmarshalStrict(scheduleYamlBytes, &patchRes))
assert.Equal(t, tc.expectedSchedule, patchRes)
eolYamlBytes, err := os.ReadFile(eolFile.Name())
require.NoError(t, err)
- eolRes := EolBranches{}
+ eolRes := model.EolBranches{}
require.NoError(t, yaml.UnmarshalStrict(eolYamlBytes, &eolRes))
assert.Equal(t, tc.expectedEolBranches, eolRes)
diff --git a/cmd/schedule-builder/cmd/root.go b/cmd/schedule-builder/cmd/root.go
index df65dbc91c0..fdb42b04348 100644
--- a/cmd/schedule-builder/cmd/root.go
+++ b/cmd/schedule-builder/cmd/root.go
@@ -24,6 +24,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
+ "k8s.io/release/cmd/schedule-builder/model"
"sigs.k8s.io/release-utils/log"
"sigs.k8s.io/release-utils/version"
"sigs.k8s.io/yaml"
@@ -153,9 +154,9 @@ func run(opts *options) error {
}
var (
- patchSchedule PatchSchedule
- releaseSchedule ReleaseSchedule
- eolBranches EolBranches
+ patchSchedule model.PatchSchedule
+ releaseSchedule model.ReleaseSchedule
+ eolBranches model.EolBranches
scheduleOut string
)
diff --git a/cmd/schedule-builder/cmd/model.go b/cmd/schedule-builder/model/model.go
similarity index 99%
rename from cmd/schedule-builder/cmd/model.go
rename to cmd/schedule-builder/model/model.go
index 894862fe2fe..bc67bf443ef 100644
--- a/cmd/schedule-builder/cmd/model.go
+++ b/cmd/schedule-builder/model/model.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package cmd
+package model
// PatchSchedule main struct to hold the schedules.
type PatchSchedule struct {
diff --git a/go.mod b/go.mod
index 01dcbe08ea1..40c4c01791f 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,8 @@ go 1.21
require (
cloud.google.com/go/storage v1.39.1
github.com/GoogleCloudPlatform/testgrid v0.0.38
+ github.com/aws/aws-lambda-go v1.47.0
+ github.com/aws/aws-sdk-go v1.51.6
github.com/blang/semver/v4 v4.0.0
github.com/cheggaaa/pb/v3 v3.1.5
github.com/go-git/go-git/v5 v5.12.0
@@ -36,8 +38,11 @@ require (
golang.org/x/net v0.24.0
golang.org/x/oauth2 v0.19.0
golang.org/x/text v0.14.0
+ gomodules.xyz/envconfig v1.3.0
google.golang.org/api v0.172.0
+ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/yaml.v2 v2.4.0
+ gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.29.3
k8s.io/utils v0.0.0-20240102154912-e7106e64919e
sigs.k8s.io/bom v0.6.0
@@ -288,11 +293,11 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
google.golang.org/grpc v1.62.1 // indirect
google.golang.org/protobuf v1.33.0 // indirect
+ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.28.4 // indirect
k8s.io/client-go v0.28.4 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
diff --git a/go.sum b/go.sum
index df915b14f7b..250a7045e3c 100644
--- a/go.sum
+++ b/go.sum
@@ -167,6 +167,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
+github.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI=
+github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
github.com/aws/aws-sdk-go v1.51.6 h1:Ld36dn9r7P9IjU8WZSaswQ8Y/XUCRpewim5980DwYiU=
github.com/aws/aws-sdk-go v1.51.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM=
@@ -1169,6 +1171,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
+gomodules.xyz/envconfig v1.3.0 h1:w1laMNVtP05uOKqmRAY6Vx7HvfPL9yc388gcVtUiI/M=
+gomodules.xyz/envconfig v1.3.0/go.mod h1:41y72mzHT7+jFNgyBpJRrZWuZJcLmLrTpq6iGgOFJMQ=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -1257,6 +1261,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1267,6 +1273,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
diff --git a/iac/patch-release-notification/README.md b/iac/patch-release-notification/README.md
new file mode 100644
index 00000000000..e136f0b99e3
--- /dev/null
+++ b/iac/patch-release-notification/README.md
@@ -0,0 +1,8 @@
+# patch-release-notification service
+
+This terraform code deploys the code to notify the K8s community about the cherry pick deadline for the patch releases.
+
+The patch-release-notification code can be found in `cmd/patch-release-notification`
+
+Right now, the terraform is applied manually by the release managers that have access to the AWS account for the SIG-release.
+
diff --git a/iac/patch-release-notification/main.tf b/iac/patch-release-notification/main.tf
new file mode 100644
index 00000000000..664c445d68f
--- /dev/null
+++ b/iac/patch-release-notification/main.tf
@@ -0,0 +1,110 @@
+provider "aws" {
+ region = var.region
+}
+
+resource "aws_sesv2_email_identity" "sig_release_email_identity" {
+ email_identity = var.email_identity
+}
+
+resource "aws_iam_role" "lambda_ses_role" {
+ name = "lambda_ses_role"
+ assume_role_policy = jsonencode({
+ Version = "2012-10-17",
+ Statement = [
+ {
+ Action = "sts:AssumeRole",
+ Effect = "Allow",
+ Principal = {
+ Service = "lambda.amazonaws.com"
+ }
+ }
+ ]
+ })
+}
+
+resource "aws_iam_policy" "lambda_ses_policy" {
+ name = "lambda_ses_policy"
+ description = "IAM policy for Lambda to access SES"
+ policy = jsonencode({
+ Version = "2012-10-17",
+ Statement = [
+ {
+ Action = [
+ "ses:SendEmail",
+ "ses:SendRawEmail"
+ ],
+ Effect = "Allow",
+ Resource = "*"
+ }
+ ]
+ })
+}
+
+resource "aws_ecr_repository" "repo" {
+ name = var.repository
+ image_tag_mutability = "MUTABLE"
+
+ image_scanning_configuration {
+ scan_on_push = false
+ }
+}
+
+resource "aws_ecr_repository" "cherry_pick_notification_repo" {
+ name = "${var.repository}/patch-release-notification"
+ image_tag_mutability = "MUTABLE"
+
+ image_scanning_configuration {
+ scan_on_push = false
+ }
+}
+
+resource "ko_build" "cherry_pick_notification_image" {
+ repo = aws_ecr_repository.cherry_pick_notification_repo.repository_url
+ base_image = "public.ecr.aws/lambda/provided:al2023"
+ working_dir = "${path.module}/../../cmd/patch-release-notification"
+ importpath = "k8s.io/release/cmd/patch-release-notification"
+}
+
+
+resource "aws_iam_role_policy_attachment" "lambda_ses_policy_attachment" {
+ role = aws_iam_role.lambda_ses_role.name
+ policy_arn = aws_iam_policy.lambda_ses_policy.arn
+}
+
+resource "aws_lambda_function" "cherry_pick_notification" {
+ function_name = "patch-release-notification"
+ role = aws_iam_role.lambda_ses_role.arn
+ image_uri = ko_build.cherry_pick_notification_image.image_ref
+ package_type = "Image"
+
+ environment {
+ variables = {
+ FROM_EMAIL = var.email_identity
+ TO_EMAIL = var.to_email
+ SCHEDULE_PATH = var.schedule_path
+ DAYS_TO_ALERT = var.days_to_alert
+ NO_MOCK = var.no_mock
+ AWS_REGION = var.region
+ }
+ }
+}
+
+resource "aws_cloudwatch_event_rule" "trigger_lambda_cron" {
+ name = "trigger-patch-release-notification-cron"
+ description = "Trigger Lambda function on a schedule"
+ schedule_expression = "cron(0 16 * * ? *)" # Example cron expression to run at 16:00 PM UTC every day
+}
+
+resource "aws_cloudwatch_event_target" "trigger_lambda_target" {
+ rule = aws_cloudwatch_event_rule.trigger_lambda_cron.name
+ target_id = "send_email_lambda"
+ arn = aws_lambda_function.cherry_pick_notification.arn
+}
+
+resource "aws_lambda_permission" "allow_cloudwatch_to_invoke_lambda" {
+ statement_id = "AllowExecutionFromCloudWatch"
+ action = "lambda:InvokeFunction"
+ function_name = aws_lambda_function.cherry_pick_notification.function_name
+ principal = "events.amazonaws.com"
+ source_arn = aws_cloudwatch_event_rule.trigger_lambda_cron.arn
+}
diff --git a/iac/patch-release-notification/outputs.tf b/iac/patch-release-notification/outputs.tf
new file mode 100644
index 00000000000..0a4c2d2023c
--- /dev/null
+++ b/iac/patch-release-notification/outputs.tf
@@ -0,0 +1,3 @@
+output "email_identity_arn" {
+ value = aws_sesv2_email_identity.sig_release_email_identity.arn
+}
diff --git a/iac/patch-release-notification/terraform.tfvars b/iac/patch-release-notification/terraform.tfvars
new file mode 100644
index 00000000000..89c1b582bba
--- /dev/null
+++ b/iac/patch-release-notification/terraform.tfvars
@@ -0,0 +1,7 @@
+region = "us-west-2"
+repository = "release-engineering"
+email_identity = "release-managers@kubernetes.io"
+to_email = "dev@kubernetes.io"
+days_to_alert = 3
+no_mock = false
+schedule_path = "https://raw.githubusercontent.com/kubernetes/website/main/data/releases/schedule.yaml"
diff --git a/iac/patch-release-notification/variables.tf b/iac/patch-release-notification/variables.tf
new file mode 100644
index 00000000000..0bb6cfb44e8
--- /dev/null
+++ b/iac/patch-release-notification/variables.tf
@@ -0,0 +1,35 @@
+variable "region" {
+ description = "The AWS region to deploy the resources"
+ type = string
+}
+
+variable "email_identity" {
+ description = "The email address or domain to verify"
+ type = string
+}
+
+variable "to_email" {
+ description = "The email address to send the notification"
+ type = string
+}
+
+variable "no_mock" {
+ description = "if will send the message to dev@kubernetes.io or just internal"
+ type = bool
+}
+
+variable "days_to_alert" {
+ description = "when to send the notification"
+ type = number
+}
+
+variable "schedule_path" {
+ description = "path where we can find the schedule.yaml"
+ type = string
+}
+
+variable "repository" {
+ description = "The ECR repository to use for the image"
+ type = string
+ default = ""
+}
diff --git a/iac/patch-release-notification/versions.tf b/iac/patch-release-notification/versions.tf
new file mode 100644
index 00000000000..55c9e86575b
--- /dev/null
+++ b/iac/patch-release-notification/versions.tf
@@ -0,0 +1,21 @@
+# Terraform version
+terraform {
+ backend "s3" {
+ bucket = "tf-state-sig-release"
+ key = "cherry-pick-notification"
+ region = "us-west-2"
+ }
+
+ required_version = "1.8.0"
+
+ required_providers {
+ ko = {
+ source = "ko-build/ko"
+ }
+
+ aws = {
+ source = "hashicorp/aws"
+ version = "5.51.1"
+ }
+ }
+}
From 021821401f7a802b8c7f0885c7f46a0257f63809 Mon Sep 17 00:00:00 2001
From: cpanato
Date: Thu, 30 May 2024 13:47:08 +0200
Subject: [PATCH 2/3] moved to k8s.io repo
Signed-off-by: cpanato
---
iac/patch-release-notification/README.md | 8 --
iac/patch-release-notification/main.tf | 110 ------------------
iac/patch-release-notification/outputs.tf | 3 -
.../terraform.tfvars | 7 --
iac/patch-release-notification/variables.tf | 35 ------
iac/patch-release-notification/versions.tf | 21 ----
6 files changed, 184 deletions(-)
delete mode 100644 iac/patch-release-notification/README.md
delete mode 100644 iac/patch-release-notification/main.tf
delete mode 100644 iac/patch-release-notification/outputs.tf
delete mode 100644 iac/patch-release-notification/terraform.tfvars
delete mode 100644 iac/patch-release-notification/variables.tf
delete mode 100644 iac/patch-release-notification/versions.tf
diff --git a/iac/patch-release-notification/README.md b/iac/patch-release-notification/README.md
deleted file mode 100644
index e136f0b99e3..00000000000
--- a/iac/patch-release-notification/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# patch-release-notification service
-
-This terraform code deploys the code to notify the K8s community about the cherry pick deadline for the patch releases.
-
-The patch-release-notification code can be found in `cmd/patch-release-notification`
-
-Right now, the terraform is applied manually by the release managers that have access to the AWS account for the SIG-release.
-
diff --git a/iac/patch-release-notification/main.tf b/iac/patch-release-notification/main.tf
deleted file mode 100644
index 664c445d68f..00000000000
--- a/iac/patch-release-notification/main.tf
+++ /dev/null
@@ -1,110 +0,0 @@
-provider "aws" {
- region = var.region
-}
-
-resource "aws_sesv2_email_identity" "sig_release_email_identity" {
- email_identity = var.email_identity
-}
-
-resource "aws_iam_role" "lambda_ses_role" {
- name = "lambda_ses_role"
- assume_role_policy = jsonencode({
- Version = "2012-10-17",
- Statement = [
- {
- Action = "sts:AssumeRole",
- Effect = "Allow",
- Principal = {
- Service = "lambda.amazonaws.com"
- }
- }
- ]
- })
-}
-
-resource "aws_iam_policy" "lambda_ses_policy" {
- name = "lambda_ses_policy"
- description = "IAM policy for Lambda to access SES"
- policy = jsonencode({
- Version = "2012-10-17",
- Statement = [
- {
- Action = [
- "ses:SendEmail",
- "ses:SendRawEmail"
- ],
- Effect = "Allow",
- Resource = "*"
- }
- ]
- })
-}
-
-resource "aws_ecr_repository" "repo" {
- name = var.repository
- image_tag_mutability = "MUTABLE"
-
- image_scanning_configuration {
- scan_on_push = false
- }
-}
-
-resource "aws_ecr_repository" "cherry_pick_notification_repo" {
- name = "${var.repository}/patch-release-notification"
- image_tag_mutability = "MUTABLE"
-
- image_scanning_configuration {
- scan_on_push = false
- }
-}
-
-resource "ko_build" "cherry_pick_notification_image" {
- repo = aws_ecr_repository.cherry_pick_notification_repo.repository_url
- base_image = "public.ecr.aws/lambda/provided:al2023"
- working_dir = "${path.module}/../../cmd/patch-release-notification"
- importpath = "k8s.io/release/cmd/patch-release-notification"
-}
-
-
-resource "aws_iam_role_policy_attachment" "lambda_ses_policy_attachment" {
- role = aws_iam_role.lambda_ses_role.name
- policy_arn = aws_iam_policy.lambda_ses_policy.arn
-}
-
-resource "aws_lambda_function" "cherry_pick_notification" {
- function_name = "patch-release-notification"
- role = aws_iam_role.lambda_ses_role.arn
- image_uri = ko_build.cherry_pick_notification_image.image_ref
- package_type = "Image"
-
- environment {
- variables = {
- FROM_EMAIL = var.email_identity
- TO_EMAIL = var.to_email
- SCHEDULE_PATH = var.schedule_path
- DAYS_TO_ALERT = var.days_to_alert
- NO_MOCK = var.no_mock
- AWS_REGION = var.region
- }
- }
-}
-
-resource "aws_cloudwatch_event_rule" "trigger_lambda_cron" {
- name = "trigger-patch-release-notification-cron"
- description = "Trigger Lambda function on a schedule"
- schedule_expression = "cron(0 16 * * ? *)" # Example cron expression to run at 16:00 PM UTC every day
-}
-
-resource "aws_cloudwatch_event_target" "trigger_lambda_target" {
- rule = aws_cloudwatch_event_rule.trigger_lambda_cron.name
- target_id = "send_email_lambda"
- arn = aws_lambda_function.cherry_pick_notification.arn
-}
-
-resource "aws_lambda_permission" "allow_cloudwatch_to_invoke_lambda" {
- statement_id = "AllowExecutionFromCloudWatch"
- action = "lambda:InvokeFunction"
- function_name = aws_lambda_function.cherry_pick_notification.function_name
- principal = "events.amazonaws.com"
- source_arn = aws_cloudwatch_event_rule.trigger_lambda_cron.arn
-}
diff --git a/iac/patch-release-notification/outputs.tf b/iac/patch-release-notification/outputs.tf
deleted file mode 100644
index 0a4c2d2023c..00000000000
--- a/iac/patch-release-notification/outputs.tf
+++ /dev/null
@@ -1,3 +0,0 @@
-output "email_identity_arn" {
- value = aws_sesv2_email_identity.sig_release_email_identity.arn
-}
diff --git a/iac/patch-release-notification/terraform.tfvars b/iac/patch-release-notification/terraform.tfvars
deleted file mode 100644
index 89c1b582bba..00000000000
--- a/iac/patch-release-notification/terraform.tfvars
+++ /dev/null
@@ -1,7 +0,0 @@
-region = "us-west-2"
-repository = "release-engineering"
-email_identity = "release-managers@kubernetes.io"
-to_email = "dev@kubernetes.io"
-days_to_alert = 3
-no_mock = false
-schedule_path = "https://raw.githubusercontent.com/kubernetes/website/main/data/releases/schedule.yaml"
diff --git a/iac/patch-release-notification/variables.tf b/iac/patch-release-notification/variables.tf
deleted file mode 100644
index 0bb6cfb44e8..00000000000
--- a/iac/patch-release-notification/variables.tf
+++ /dev/null
@@ -1,35 +0,0 @@
-variable "region" {
- description = "The AWS region to deploy the resources"
- type = string
-}
-
-variable "email_identity" {
- description = "The email address or domain to verify"
- type = string
-}
-
-variable "to_email" {
- description = "The email address to send the notification"
- type = string
-}
-
-variable "no_mock" {
- description = "if will send the message to dev@kubernetes.io or just internal"
- type = bool
-}
-
-variable "days_to_alert" {
- description = "when to send the notification"
- type = number
-}
-
-variable "schedule_path" {
- description = "path where we can find the schedule.yaml"
- type = string
-}
-
-variable "repository" {
- description = "The ECR repository to use for the image"
- type = string
- default = ""
-}
diff --git a/iac/patch-release-notification/versions.tf b/iac/patch-release-notification/versions.tf
deleted file mode 100644
index 55c9e86575b..00000000000
--- a/iac/patch-release-notification/versions.tf
+++ /dev/null
@@ -1,21 +0,0 @@
-# Terraform version
-terraform {
- backend "s3" {
- bucket = "tf-state-sig-release"
- key = "cherry-pick-notification"
- region = "us-west-2"
- }
-
- required_version = "1.8.0"
-
- required_providers {
- ko = {
- source = "ko-build/ko"
- }
-
- aws = {
- source = "hashicorp/aws"
- version = "5.51.1"
- }
- }
-}
From b12ab02b779d614f365f17e1b047ae3a53548cc2 Mon Sep 17 00:00:00 2001
From: cpanato
Date: Thu, 30 May 2024 14:41:41 +0200
Subject: [PATCH 3/3] apply changes per feedback review
Signed-off-by: cpanato
---
cmd/patch-release-notification/main.go | 45 +++++++++----------
.../templates/email.tmpl | 6 +--
2 files changed, 24 insertions(+), 27 deletions(-)
diff --git a/cmd/patch-release-notification/main.go b/cmd/patch-release-notification/main.go
index 77555faea24..f08372554ff 100644
--- a/cmd/patch-release-notification/main.go
+++ b/cmd/patch-release-notification/main.go
@@ -22,7 +22,6 @@ import (
"embed"
"fmt"
"io"
- "log"
"math"
"net/http"
"os"
@@ -91,7 +90,7 @@ func getConfig() (*Config, error) {
return &c, nil
}
-func New(ctx context.Context) (*Options, error) {
+func NewOptions(ctx context.Context) (*Options, error) {
config, err := getConfig()
if err != nil {
return nil, fmt.Errorf("failed to get config: %w", err)
@@ -102,7 +101,6 @@ func New(ctx context.Context) (*Options, error) {
Region: aws.String(config.AWSRegion),
})
if err != nil {
- log.Println("Error occurred while creating aws session", err)
return nil, err
}
@@ -114,7 +112,7 @@ func New(ctx context.Context) (*Options, error) {
}
func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { //nolint: gocritic
- o, err := New(ctx)
+ o, err := NewOptions(ctx)
if err != nil {
return events.APIGatewayProxyResponse{
Body: `{"status": "nok"}`,
@@ -122,12 +120,13 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
}, err
}
+ logrus.Infof("Will pull the patch release schedule from: %s", o.Config.SchedulePath)
data, err := loadFileOrURL(o.Config.SchedulePath)
if err != nil {
return events.APIGatewayProxyResponse{
Body: `{"status": "nok"}`,
StatusCode: http.StatusInternalServerError,
- }, fmt.Errorf("failed to read the file: %w", err)
+ }, fmt.Errorf("reading the file: %w", err)
}
patchSchedule := &model.PatchSchedule{}
@@ -138,7 +137,7 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
return events.APIGatewayProxyResponse{
Body: `{"status": "nok"}`,
StatusCode: http.StatusInternalServerError,
- }, fmt.Errorf("failed to decode the file: %w", err)
+ }, fmt.Errorf("decoding the file: %w", err)
}
output := &Template{}
@@ -157,7 +156,7 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
currentTime := time.Now().UTC()
days := t.Sub(currentTime).Hours() / 24
intDay, _ := math.Modf(days)
- logrus.Infof("cherry pick deadline: %d, days to alert: %d", int(intDay), o.Config.DaysToAlert)
+ logrus.Infof("Cherry pick deadline: %d, days to alert: %d", int(intDay), o.Config.DaysToAlert)
if int(intDay) == o.Config.DaysToAlert {
output.Releases = append(output.Releases, TemplateRelease{
Release: patch.Release,
@@ -167,6 +166,14 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
}
}
+ if !shouldSendEmail {
+ logrus.Info("No email is needed to send")
+ return events.APIGatewayProxyResponse{
+ Body: `{"status": "ok"}`,
+ StatusCode: http.StatusOK,
+ }, nil
+ }
+
tmpl, err := template.ParseFS(tpls, "templates/email.tmpl")
if err != nil {
return events.APIGatewayProxyResponse{
@@ -181,19 +188,11 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
return events.APIGatewayProxyResponse{
Body: `{"status": "nok"}`,
StatusCode: http.StatusInternalServerError,
- }, fmt.Errorf("parsing values to the template: %w", err)
- }
-
- if !shouldSendEmail {
- logrus.Info("No email is needed to send")
- return events.APIGatewayProxyResponse{
- Body: `{"status": "ok"}`,
- StatusCode: http.StatusOK,
- }, nil
+ }, fmt.Errorf("executing the template: %w", err)
}
logrus.Info("Sending mail")
- subject := "[Please Read] Patch Releases cherry-pick deadline"
+ subject := "[Please Read] Upcoming Patch Releases Cherry-Pick Deadline for Kubernetes"
fromEmail := o.Config.FromEmail
recipient := Recipient{
@@ -212,7 +211,7 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
return events.APIGatewayProxyResponse{
Body: `{"status": "nok"}`,
StatusCode: http.StatusInternalServerError,
- }, fmt.Errorf("parsing values to the template: %w", err)
+ }, fmt.Errorf("sending the email: %w", err)
}
return events.APIGatewayProxyResponse{
@@ -240,9 +239,6 @@ func (o *Options) SendEmailRawSES(messageBody, subject, fromEmail string, recipi
recipients = append(recipients, &recipient)
}
- // Set to emails
- msg.SetHeader("To", recipient.toEmails...)
-
// cc mails mentioned
if len(recipient.ccEmails) != 0 {
// Need to add cc mail IDs also in recipient list
@@ -275,7 +271,7 @@ func (o *Options) SendEmailRawSES(messageBody, subject, fromEmail string, recipi
var emailRaw bytes.Buffer
_, err := msg.WriteTo(&emailRaw)
if err != nil {
- log.Printf("failed to write mail: %v\n", err)
+ logrus.Errorf("Failed to write mail: %v", err)
return err
}
@@ -287,11 +283,11 @@ func (o *Options) SendEmailRawSES(messageBody, subject, fromEmail string, recipi
// send raw email
_, err = svc.SendRawEmail(input)
if err != nil {
- log.Println("Error sending mail - ", err)
+ logrus.Errorf("Error sending mail - %v", err)
return err
}
- log.Println("Email sent successfully to: ", recipient.toEmails)
+ logrus.Infof("Email sent successfully to: %q", recipient.toEmails)
return nil
}
@@ -314,5 +310,6 @@ func loadFileOrURL(fileRef string) ([]byte, error) {
return nil, err
}
}
+
return raw, nil
}
diff --git a/cmd/patch-release-notification/templates/email.tmpl b/cmd/patch-release-notification/templates/email.tmpl
index 6fbdd7704e9..49d464f6407 100644
--- a/cmd/patch-release-notification/templates/email.tmpl
+++ b/cmd/patch-release-notification/templates/email.tmpl
@@ -7,7 +7,7 @@
{{end}}
Here are some quick links to search for cherry-pick PRs:
{{range .Releases}}
- - release-{{ .Release }}: https://github.com/kubernetes/kubernetes/pulls?q=is%3Apr+is%3Aopen+base%3Arelease-{{ .Release }}+label%3Ado-not-merge%2Fcherry-pick-not-approved
+ - Check PRs for release-{{ .Release }}
{{end}}
For PRs that you intend to land for the upcoming patch sets, please
@@ -18,7 +18,7 @@ ensure they have:
- /priority
- /lgtm
- /approve
- - passing tests
+ - passing tests and not on hold.
Details on the cherry-pick process can be found here:
https://git.k8s.io/community/contributors/devel/sig-release/cherry-picks.md
@@ -27,6 +27,6 @@ ensure they have:
If you have any questions for the Release Managers, please feel free to
reach out to us at #release-management (Kubernetes Slack) or release-managers@kubernetes.io
We wish everyone a happy and safe week!
-SIG-Release Team
+SIG Release Team