blob: 553af286e415be049f077e968e8c60d1127c9c7f [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package task
import (
"embed"
"fmt"
"strings"
"text/template"
"golang.org/x/build/maintner/maintnerd/maintapi/version"
)
type ReleaseAnnouncement struct {
// Version is the Go version that has been released.
//
// The version string must use the same format as Go tags. For example:
// • "go1.17.2" for a minor Go release
// • "go1.18" for a major Go release
// • "go1.18beta1" or "go1.18rc1" for a pre-release
Version string
// SecondaryVersion is an older Go version that was also released.
// This only applies to minor releases. For example, "go1.16.10".
SecondaryVersion string
// Security is a list of descriptions, one for each distinct
// security fix included in this release, in Markdown format.
//
// The empty list means there are no security fixes included.
//
// This field applies only to minor releases; it is an error
// to try to use it another release type.
Security []string
// Names is an optional list of release coordinator names to
// include in the sign-off message.
Names []string
}
type mailContent struct {
Subject string
BodyHTML string
BodyText string
}
// announcementMail generates the announcement email for release r.
func announcementMail(r ReleaseAnnouncement) (mailContent, error) {
// Pick a template name for this type of release.
var name string
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)
}
// TODO(go.dev/issue/47405): Render the announcement template.
// Get the email subject.
// Render the email body.
return mailContent{}, fmt.Errorf("not implemented yet")
}
// announceTmpl holds templates for Go release announcement emails.
//
// Each email template starts with a MIME-style header with a Subject key,
// and the rest of it is Markdown for the email body.
var announceTmpl = template.Must(template.New("").Funcs(template.FuncMap{
"join": func(s []string) string {
switch len(s) {
case 0:
return ""
case 1:
return s[0]
case 2:
return s[0] + " and " + s[1]
default:
return strings.Join(s[:len(s)-1], ", ") + ", and " + s[len(s)-1]
}
},
"indent": func(s string) string { return "\t" + strings.ReplaceAll(s, "\n", "\n\t") },
// subjectPrefix returns the email subject prefix for release r, if any.
"subjectPrefix": func(r ReleaseAnnouncement) string {
switch {
case len(r.Security) > 0:
// Include a security prefix as documented at https://go.dev/security#receiving-security-updates:
//
// > The best way to receive security announcements is to subscribe to the golang-announce mailing list.
// > Any messages pertaining to a security issue will be prefixed with [security].
//
return "[security]"
default:
return ""
}
},
// short and helpers below manipulate valid Go version strings
// for the current needs of the announcement templates.
"short": func(v string) string { return strings.TrimPrefix(v, "go") },
// major extracts the major part of a valid Go version.
// For example, major("go1.18.4") == "1.18".
"major": func(v string) (string, error) {
x, ok := version.Go1PointX(v)
if !ok {
return "", fmt.Errorf("internal error: version.Go1PointX(%q) is not ok", v)
}
return fmt.Sprintf("1.%d", x), nil
},
// build extracts the pre-release build number of a valid Go version.
// For example, build("go1.19beta2") == "2".
"build": func(v string) (string, error) {
if i := strings.Index(v, "beta"); i != -1 {
return v[i+len("beta"):], nil
} else if i := strings.Index(v, "rc"); i != -1 {
return v[i+len("rc"):], nil
}
return "", fmt.Errorf("internal error: unhandled pre-release Go version %q", v)
},
}).ParseFS(tmplDir, "template/announce-*.md"))
//go:embed template
var tmplDir embed.FS