diff --git a/cmd/relui/main.go b/cmd/relui/main.go index c91a41f6bb..63e3f5c4af 100644 --- a/cmd/relui/main.go +++ b/cmd/relui/main.go @@ -69,9 +69,9 @@ func main() { } sendgridAPIKey := secret.Flag("sendgrid-api-key", "SendGrid API key for workflows involving sending email.") var annMail task.MailHeader - addressVarFlag(&annMail.From, "announce-mail-from", "The From address to use for the announcement mail.") - addressVarFlag(&annMail.To, "announce-mail-to", "The To address to use for the announcement mail.") - addressListVarFlag(&annMail.BCC, "announce-mail-bcc", "The BCC address list to use for the announcement mail.") + addressVarFlag(&annMail.From, "announce-mail-from", "The From address to use for the (pre-)announcement mail.") + addressVarFlag(&annMail.To, "announce-mail-to", "The To address to use for the (pre-)announcement mail.") + addressListVarFlag(&annMail.BCC, "announce-mail-bcc", "The BCC address list to use for the (pre-)announcement mail.") var twitterAPI secret.TwitterCredentials secret.JSONVarFlag(&twitterAPI, "twitter-api-secret", "Twitter API secret to use for workflows involving tweeting.") masterKey := secret.Flag("builder-master-key", "Builder master key") diff --git a/internal/gophers/gophers.go b/internal/gophers/gophers.go index 01d4564412..0c3a089b7e 100644 --- a/internal/gophers/gophers.go +++ b/internal/gophers/gophers.go @@ -2640,6 +2640,7 @@ func init() { addPerson("Taro Aoki", "aizu.s1230022@gmail.com", "@ktr0731") addPerson("Tarrant", "tarrant@keyneston.com", "@tarrant") addPerson("Taru Karttunen", "taruti@taruti.net", "@taruti") + addPerson("Tatiana Bradley", "tatiana@golang.org") addPerson("Tatsuhiro Tsujikawa", "tatsuhiro.t@gmail.com", "@tatsuhiro-t") addPerson("Taufiq Rahman", "taufiqrx8@gmail.com", "@Inconnu08") addPerson("Ted Hahn", "teh@uber.com") diff --git a/internal/relui/templates/new_workflow.html b/internal/relui/templates/new_workflow.html index 6778680304..9705b01ab5 100644 --- a/internal/relui/templates/new_workflow.html +++ b/internal/relui/templates/new_workflow.html @@ -27,8 +27,19 @@

New Go Release

{{range $_, $p := .Selected.Parameters}} - {{if eq $p.Type.String "string"}} -
+ {{if eq $p.HTMLElement "select"}} +
+ + +
+ {{else if or (eq $p.Type.String "string") (eq $p.Type.String "task.Date")}} +
0 && name != "announce-minor.md" { - // The Security field isn't supported in templates other than minor, - // so report an error instead of silently dropping it. - // - // Note: Maybe in the future we'd want to consider support for including sentences like - // "This beta release includes the same security fixes as in Go X.Y.Z and Go A.B.C.", - // but we'll have a better idea after these initial templates get more practical use. - return mailContent{}, fmt.Errorf("email template %q doesn't support the Security field; this field can only be used in minor releases", name) - } else if r.SecondaryVersion != "" && name != "announce-minor.md" { - return mailContent{}, fmt.Errorf("email template %q doesn't support more than one release; the SecondaryVersion field can only be used in minor releases", name) + switch r := data.(type) { + case releaseAnnouncement: + if i := strings.Index(r.Version, "beta"); i != -1 { // A beta release. + name = "announce-beta.md" + } else if i := strings.Index(r.Version, "rc"); i != -1 { // Release Candidate. + name = "announce-rc.md" + } else if strings.Count(r.Version, ".") == 1 { // Major release like "go1.X". + name = "announce-major.md" + } else if strings.Count(r.Version, ".") == 2 { // Minor release like "go1.X.Y". + name = "announce-minor.md" + } else { + return mailContent{}, fmt.Errorf("unknown version format: %q", r.Version) + } + + if len(r.Security) > 0 && name != "announce-minor.md" { + // The Security field isn't supported in templates other than minor, + // so report an error instead of silently dropping it. + // + // Note: Maybe in the future we'd want to consider support for including sentences like + // "This beta release includes the same security fixes as in Go X.Y.Z and Go A.B.C.", + // but we'll have a better idea after these initial templates get more practical use. + return mailContent{}, fmt.Errorf("email template %q doesn't support the Security field; this field can only be used in minor releases", name) + } else if r.SecondaryVersion != "" && name != "announce-minor.md" { + return mailContent{}, fmt.Errorf("email template %q doesn't support more than one release; the SecondaryVersion field can only be used in minor releases", name) + } + case releasePreAnnouncement: + name = "pre-announce-minor.md" + default: + return mailContent{}, fmt.Errorf("unknown template data type %T", data) } - // Render the announcement email template. + // Render the (pre-)announcement email template. // // It'll produce a valid message with a MIME header and a body, so parse it as such. var buf bytes.Buffer - if err := announceTmpl.ExecuteTemplate(&buf, name, r); err != nil { + if err := announceTmpl.ExecuteTemplate(&buf, name, data); err != nil { return mailContent{}, err } m, err := mail.ReadMessage(&buf) @@ -292,7 +412,7 @@ var announceTmpl = template.Must(template.New("").Funcs(template.FuncMap{ } return "", fmt.Errorf("internal error: unhandled pre-release Go version %q", v) }, -}).ParseFS(tmplDir, "template/announce-*.md")) +}).ParseFS(tmplDir, "template/announce-*.md", "template/pre-announce-minor.md")) //go:embed template var tmplDir embed.FS diff --git a/internal/task/announce_test.go b/internal/task/announce_test.go index 1954bdb943..1f5cdb363c 100644 --- a/internal/task/announce_test.go +++ b/internal/task/announce_test.go @@ -34,11 +34,11 @@ func TestAnnounceReleaseShortContext(t *testing.T) { func TestAnnouncementMail(t *testing.T) { tests := [...]struct { name string - in releaseAnnouncement + in any wantSubject string }{ { - name: "minor", + name: "announce-minor", in: releaseAnnouncement{ Version: "go1.18.1", SecondaryVersion: "go1.17.9", @@ -47,7 +47,7 @@ func TestAnnouncementMail(t *testing.T) { wantSubject: "Go 1.18.1 and Go 1.17.9 are released", }, { - name: "minor-with-security", + name: "announce-minor-with-security", in: releaseAnnouncement{ Version: "go1.18.1", SecondaryVersion: "go1.17.9", @@ -80,7 +80,7 @@ This is CVE-2022-27536 and https://go.dev/issue/51759.`, wantSubject: "[security] Go 1.18.1 and Go 1.17.9 are released", }, { - name: "minor-solo", + name: "announce-minor-solo", in: releaseAnnouncement{ Version: "go1.11.1", Security: []string{"abc: security fix 1", "xyz: security fix 2"}, @@ -89,26 +89,48 @@ This is CVE-2022-27536 and https://go.dev/issue/51759.`, wantSubject: "[security] Go 1.11.1 is released", }, { - name: "beta", + name: "announce-beta", in: releaseAnnouncement{ Version: "go1.19beta5", }, wantSubject: "Go 1.19 Beta 5 is released", }, { - name: "rc", + name: "announce-rc", in: releaseAnnouncement{ Version: "go1.19rc6", }, wantSubject: "Go 1.19 Release Candidate 6 is released", }, { - name: "major", + name: "announce-major", in: releaseAnnouncement{ Version: "go1.19", }, wantSubject: "Go 1.19 is released", }, + + { + name: "pre-announce-minor", + in: releasePreAnnouncement{ + Target: Date{2022, time.July, 12}, + Version: "go1.18.4", + SecondaryVersion: "go1.17.12", + Security: "the standard library", + Names: []string{"Alice"}, + }, + wantSubject: "[security] Go 1.18.4 and Go 1.17.12 pre-announcement", + }, + { + name: "pre-announce-minor-solo", + in: releasePreAnnouncement{ + Target: Date{2022, time.July, 12}, + Version: "go1.18.4", + Security: "the toolchain", + Names: []string{"Alice", "Bob"}, + }, + wantSubject: "[security] Go 1.18.4 pre-announcement", + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { @@ -117,17 +139,17 @@ This is CVE-2022-27536 and https://go.dev/issue/51759.`, t.Fatal("announcementMail returned non-nil error:", err) } if *updateFlag { - writeTestdataFile(t, "announce-"+tc.name+".html", []byte(m.BodyHTML)) - writeTestdataFile(t, "announce-"+tc.name+".txt", []byte(m.BodyText)) + writeTestdataFile(t, tc.name+".html", []byte(m.BodyHTML)) + writeTestdataFile(t, tc.name+".txt", []byte(m.BodyText)) return } if diff := cmp.Diff(tc.wantSubject, m.Subject); diff != "" { t.Errorf("subject mismatch (-want +got):\n%s", diff) } - if diff := cmp.Diff(testdataFile(t, "announce-"+tc.name+".html"), m.BodyHTML); diff != "" { + if diff := cmp.Diff(testdataFile(t, tc.name+".html"), m.BodyHTML); diff != "" { t.Errorf("body HTML mismatch (-want +got):\n%s", diff) } - if diff := cmp.Diff(testdataFile(t, "announce-"+tc.name+".txt"), m.BodyText); diff != "" { + if diff := cmp.Diff(testdataFile(t, tc.name+".txt"), m.BodyText); diff != "" { t.Errorf("body text mismatch (-want +got):\n%s", diff) } if t.Failed() { @@ -245,6 +267,73 @@ Heschi and Dmitri for the Go team` + "\n", } } +func TestPreAnnounceRelease(t *testing.T) { + if testing.Short() { + t.Skip("not running test that uses internet in short mode") + } + + tests := [...]struct { + name string + versions []string + target Date + security string + coordinators []string + want SentMail + wantLog string + }{ + { + name: "minor", + versions: []string{"go1.18.4", "go1.17.11"}, // Intentionally not 1.17.12 so the real email doesn't get in the way. + target: Date{2022, time.July, 12}, + security: "the standard library", + coordinators: []string{"tatiana"}, + want: SentMail{Subject: "[security] Go 1.18.4 and Go 1.17.11 pre-announcement"}, + wantLog: `pre-announcement subject: [security] Go 1.18.4 and Go 1.17.11 pre-announcement + +pre-announcement body HTML: +

Hello gophers,

+

We plan to issue Go 1.18.4 and Go 1.17.11 on Tuesday, July 12.

+

These minor releases include PRIVATE security fixes to the standard library.

+

Following our security policy, this is the pre-announcement of those releases.

+

Thanks,
+Tatiana for the Go team

+ +pre-announcement body text: +Hello gophers, + +We plan to issue Go 1.18.4 and Go 1.17.11 on Tuesday, July 12. + +These minor releases include PRIVATE security fixes to the standard library. + +Following our security policy, this is the pre-announcement of those releases. + +Thanks, +Tatiana for the Go team` + "\n", + }, + // TestAnnouncementMail has additional coverage. + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tasks := AnnounceMailTasks{ + SendMail: func(h MailHeader, c mailContent) error { return nil }, + testHookNow: func() time.Time { return time.Date(2022, time.July, 7, 0, 0, 0, 0, time.UTC) }, + } + var buf bytes.Buffer + ctx := &workflow.TaskContext{Context: context.Background(), Logger: fmtWriter{&buf}} + sentMail, err := tasks.PreAnnounceRelease(ctx, tc.versions, tc.target, tc.security, tc.coordinators) + if err != nil { + t.Fatal("task function returned non-nil error:", err) + } + if diff := cmp.Diff(tc.want, sentMail); diff != "" { + t.Errorf("sent mail mismatch (-want +got):\n%s", diff) + } + if diff := cmp.Diff(tc.wantLog, buf.String()); diff != "" { + t.Errorf("log mismatch (-want +got):\n%s", diff) + } + }) + } +} + func TestFindGoogleGroupsThread(t *testing.T) { if testing.Short() { t.Skip("not running test that uses internet in short mode") diff --git a/internal/task/template/pre-announce-minor.md b/internal/task/template/pre-announce-minor.md new file mode 100644 index 0000000000..79bd071608 --- /dev/null +++ b/internal/task/template/pre-announce-minor.md @@ -0,0 +1,15 @@ +Subject: [security] Go {{short .Version}}{{with .SecondaryVersion}} and Go {{. | short}}{{end}} pre-announcement + +Hello gophers, + +We plan to issue Go {{short .Version}}{{with .SecondaryVersion}} and Go {{. | short}}{{end}} on {{.Target.Format "Monday, January 2"}}. + +{{if .SecondaryVersion -}} +These minor releases include +{{- else -}} +This minor release includes{{end}} PRIVATE security fixes to {{.Security}}. + +Following our security policy, this is the pre-announcement of {{if .SecondaryVersion}}those releases{{else}}the release{{end}}. + +Thanks, +{{with .Names}}{{join .}} for the{{else}}The{{end}} Go team diff --git a/internal/task/testdata/pre-announce-minor-solo.html b/internal/task/testdata/pre-announce-minor-solo.html new file mode 100644 index 0000000000..209757f812 --- /dev/null +++ b/internal/task/testdata/pre-announce-minor-solo.html @@ -0,0 +1,6 @@ +

Hello gophers,

+

We plan to issue Go 1.18.4 on Tuesday, July 12.

+

This minor release includes PRIVATE security fixes to the toolchain.

+

Following our security policy, this is the pre-announcement of the release.

+

Thanks,
+Alice and Bob for the Go team

diff --git a/internal/task/testdata/pre-announce-minor-solo.txt b/internal/task/testdata/pre-announce-minor-solo.txt new file mode 100644 index 0000000000..2a06bb4988 --- /dev/null +++ b/internal/task/testdata/pre-announce-minor-solo.txt @@ -0,0 +1,10 @@ +Hello gophers, + +We plan to issue Go 1.18.4 on Tuesday, July 12. + +This minor release includes PRIVATE security fixes to the toolchain. + +Following our security policy, this is the pre-announcement of the release. + +Thanks, +Alice and Bob for the Go team diff --git a/internal/task/testdata/pre-announce-minor.html b/internal/task/testdata/pre-announce-minor.html new file mode 100644 index 0000000000..2a6a949916 --- /dev/null +++ b/internal/task/testdata/pre-announce-minor.html @@ -0,0 +1,6 @@ +

Hello gophers,

+

We plan to issue Go 1.18.4 and Go 1.17.12 on Tuesday, July 12.

+

These minor releases include PRIVATE security fixes to the standard library.

+

Following our security policy, this is the pre-announcement of those releases.

+

Thanks,
+Alice for the Go team

diff --git a/internal/task/testdata/pre-announce-minor.txt b/internal/task/testdata/pre-announce-minor.txt new file mode 100644 index 0000000000..ef760f49d6 --- /dev/null +++ b/internal/task/testdata/pre-announce-minor.txt @@ -0,0 +1,10 @@ +Hello gophers, + +We plan to issue Go 1.18.4 and Go 1.17.12 on Tuesday, July 12. + +These minor releases include PRIVATE security fixes to the standard library. + +Following our security policy, this is the pre-announcement of those releases. + +Thanks, +Alice for the Go team diff --git a/internal/task/version.go b/internal/task/version.go index e1a17343ec..a6101a54ce 100644 --- a/internal/task/version.go +++ b/internal/task/version.go @@ -42,6 +42,19 @@ func (t *VersionTasks) tagInfo(ctx context.Context) (tags map[string]bool, curre return tags, currentMajor, nil } +// GetNextVersions returns the next for each of the given types of release. +func (t *VersionTasks) GetNextVersions(ctx context.Context, kinds []ReleaseKind) ([]string, error) { + var next []string + for _, k := range kinds { + n, err := t.GetNextVersion(ctx, k) + if err != nil { + return nil, err + } + next = append(next, n) + } + return next, nil +} + // GetNextVersion returns the next for the given type of release. func (t *VersionTasks) GetNextVersion(ctx context.Context, kind ReleaseKind) (string, error) { tags, currentMajor, err := t.tagInfo(ctx) diff --git a/internal/workflow/workflow.go b/internal/workflow/workflow.go index fa914ce67b..c68e147a24 100644 --- a/internal/workflow/workflow.go +++ b/internal/workflow/workflow.go @@ -125,6 +125,7 @@ type MetaParameter interface { Type() reflect.Type HTMLElement() string HTMLInputType() string + HTMLSelectOptions() []string Doc() string Example() string } @@ -145,12 +146,13 @@ type parameter[T any] struct { d ParamDef[T] } -func (p parameter[T]) Name() string { return p.d.Name } -func (p parameter[T]) Type() reflect.Type { return p.typ() } -func (p parameter[T]) HTMLElement() string { return p.d.HTMLElement } -func (p parameter[T]) HTMLInputType() string { return p.d.HTMLInputType } -func (p parameter[T]) Doc() string { return p.d.Doc } -func (p parameter[T]) Example() string { return p.d.Example } +func (p parameter[T]) Name() string { return p.d.Name } +func (p parameter[T]) Type() reflect.Type { return p.typ() } +func (p parameter[T]) HTMLElement() string { return p.d.HTMLElement } +func (p parameter[T]) HTMLInputType() string { return p.d.HTMLInputType } +func (p parameter[T]) HTMLSelectOptions() []string { return p.d.HTMLSelectOptions } +func (p parameter[T]) Doc() string { return p.d.Doc } +func (p parameter[T]) Example() string { return p.d.Example } func (p parameter[T]) RequireNonZero() bool { return !strings.HasSuffix(p.d.Name, " (optional)") } @@ -169,12 +171,15 @@ func (p parameter[T]) dependencies() []*taskDefinition { return nil } // there are some HTML-related knobs available. type ParamType[T any] struct { // HTMLElement configures the HTML element for entering the parameter value. - // Supported values are "input" and "textarea". + // Supported values are "input", "textarea" and "select". HTMLElement string // HTMLInputType optionally configures the type attribute when HTMLElement is "input". // If this attribute is not specified, elements default to type="text". // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types. HTMLInputType string + // HTMLSelectOptions configures the available options when HTMLElement is "select". + // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option. + HTMLSelectOptions []string } var (