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..f08372554ff --- /dev/null +++ b/cmd/patch-release-notification/main.go @@ -0,0 +1,315 @@ +/* +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" + + "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 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 // 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 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=