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..f6a6513520e --- /dev/null +++ b/cmd/patch-release-notification/main.go @@ -0,0 +1,331 @@ +/* +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" + "math" + "net/http" + "os" + "path/filepath" + "strings" + "text/template" + "time" + + "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" + "gomodules.xyz/envconfig" + "gopkg.in/gomail.v2" + "gopkg.in/yaml.v3" + + "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 //nolint:containedctx // Can't remove this without a breaking change +} + +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 NewOptions(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 { + 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 := NewOptions(ctx) + if err != nil { + return events.APIGatewayProxyResponse{ + Body: `{"status": "nok"}`, + StatusCode: http.StatusInternalServerError, + }, 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("reading 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("decoding 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 + } + } + + 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{ + 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("executing the template: %w", err) + } + + logrus.Info("Sending mail") + + subject := "[Please Read] Upcoming Patch Releases Cherry-Pick Deadline for Kubernetes" + 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("sending the email: %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) + } + + // 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 { + logrus.Errorf("Failed to write mail: %v", 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 { + logrus.Errorf("Error sending mail - %v", err) + + return err + } + + logrus.Infof("Email sent successfully to: %q", 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,noctx // 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..49d464f6407 --- /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}} +- Check PRs for release-{{ .Release }}
+{{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 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
+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 b366c900a14..eb11bb3a5f4 100644 --- a/cmd/schedule-builder/cmd/markdown.go +++ b/cmd/schedule-builder/cmd/markdown.go @@ -30,13 +30,15 @@ import ( "sigs.k8s.io/release-utils/util" "sigs.k8s.io/yaml" + + "k8s.io/release/cmd/schedule-builder/model" ) //go:embed templates/*.tmpl 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 { @@ -107,11 +109,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 } @@ -119,7 +121,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 { @@ -153,7 +155,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 @@ -200,7 +202,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 { @@ -224,7 +226,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, @@ -245,7 +247,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) @@ -304,7 +306,7 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, eolBranches 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), @@ -314,7 +316,7 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, eolBranches } } - newSchedules := []*Schedule{} + newSchedules := []*model.Schedule{} for i, sched := range schedule.Schedules { appendItem := true @@ -334,7 +336,7 @@ func updatePatchSchedule(refTime time.Time, schedule PatchSchedule, eolBranches schedule.Schedules = newSchedules - newUpcomingReleases := []*PatchRelease{} + newUpcomingReleases := []*model.PatchRelease{} latestDate := refTime for _, upcomingRelease := range schedule.UpcomingReleases { @@ -369,7 +371,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 d94c30a6fe8..586512b4b25 100644 --- a/cmd/schedule-builder/cmd/markdown_test.go +++ b/cmd/schedule-builder/cmd/markdown_test.go @@ -26,6 +26,8 @@ import ( "github.com/stretchr/testify/require" "sigs.k8s.io/yaml" + + "k8s.io/release/cmd/schedule-builder/model" ) const expectedPatchSchedule = `### Upcoming Monthly Releases @@ -135,22 +137,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", @@ -165,7 +167,7 @@ func TestParsePatchSchedule(t *testing.T) { }, }, }, - UpcomingReleases: []*PatchRelease{ + UpcomingReleases: []*model.PatchRelease{ { CherryPickDeadline: "2020-06-12", TargetDate: "2020-06-17", @@ -175,18 +177,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", @@ -206,7 +208,7 @@ func TestParsePatchSchedule(t *testing.T) { }, }, }, - UpcomingReleases: []*PatchRelease{ + UpcomingReleases: []*model.PatchRelease{ { CherryPickDeadline: "2020-06-12", TargetDate: "2020-06-17", @@ -226,15 +228,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", @@ -347,17 +349,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", @@ -371,14 +373,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-12", @@ -393,18 +395,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-12", TargetDate: "2024-04-17", }, EndOfLifeDate: "2025-01-01", MaintenanceModeStartDate: "2024-12-01", - PreviousPatches: []*PatchRelease{ + PreviousPatches: []*model.PatchRelease{ { Release: "1.30.3", CherryPickDeadline: "2024-03-08", @@ -426,7 +428,7 @@ func TestUpdatePatchSchedule(t *testing.T) { Release: "1.29", }, }, - UpcomingReleases: []*PatchRelease{ + UpcomingReleases: []*model.PatchRelease{ { CherryPickDeadline: "2024-04-12", TargetDate: "2024-04-17", @@ -441,8 +443,8 @@ func TestUpdatePatchSchedule(t *testing.T) { }, }, }, - expectedEolBranches: EolBranches{ - Branches: []*EolBranch{ + expectedEolBranches: model.EolBranches{ + Branches: []*model.EolBranch{ { Release: "1.20", FinalPatchRelease: "1.20.10", @@ -461,12 +463,12 @@ 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) @@ -474,7 +476,7 @@ func TestUpdatePatchSchedule(t *testing.T) { 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 e4bf449de90..79deb723aa4 100644 --- a/cmd/schedule-builder/cmd/root.go +++ b/cmd/schedule-builder/cmd/root.go @@ -27,6 +27,8 @@ import ( "sigs.k8s.io/release-utils/log" "sigs.k8s.io/release-utils/version" "sigs.k8s.io/yaml" + + "k8s.io/release/cmd/schedule-builder/model" ) // rootCmd represents the base command when called without any subcommands. @@ -155,9 +157,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 066ff55c324..b64bf6b27de 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ toolchain go1.24.2 require ( cloud.google.com/go/storage v1.49.0 github.com/GoogleCloudPlatform/testgrid v0.0.38 + github.com/aws/aws-lambda-go v1.47.0 + github.com/aws/aws-sdk-go v1.55.6 github.com/blang/semver/v4 v4.0.0 github.com/cheggaaa/pb/v3 v3.1.7 github.com/go-git/go-git/v5 v5.16.0 @@ -37,8 +39,11 @@ require ( golang.org/x/net v0.40.0 golang.org/x/oauth2 v0.30.0 golang.org/x/text v0.25.0 + gomodules.xyz/envconfig v1.3.0 google.golang.org/api v0.221.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.33.0 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 sigs.k8s.io/bom v0.6.0 @@ -194,6 +199,7 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect github.com/jellydator/ttlcache/v3 v3.3.0 // indirect + github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect @@ -303,10 +309,10 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect google.golang.org/grpc v1.71.1 // indirect google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // 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.130.1 // indirect diff --git a/go.sum b/go.sum index 59d586ee9b9..3d667ee6f29 100644 --- a/go.sum +++ b/go.sum @@ -187,6 +187,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= +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.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E= @@ -599,6 +601,8 @@ github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHT github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw= github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY= github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -1209,6 +1213,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-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/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= @@ -1297,6 +1303,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.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +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= @@ -1305,6 +1313,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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/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=