Skip to content

🌱 Add validation for PREVIOUS_RELEASE_TAG in release-notes-tool #12380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions hack/tools/release/internal/update_providers/provider_issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,17 @@ type IssueResponse struct {
// releaseDetails is the struct for the release details.
type releaseDetails struct {
ReleaseTag string
BetaTag string
PreReleaseTag string
ReleaseLink string
ReleaseDate string
ReleaseNotesLink string
}

// Example command:
//
// GITHUB_ISSUE_OPENER_TOKEN="fake" RELEASE_TAG="v1.6.0-alpha.0" RELEASE_DATE="2023-11-28" PROVIDER_ISSUES_DRY_RUN="true" make release-provider-issues-tool
// GITHUB_ISSUE_OPENER_TOKEN="fake" RELEASE_TAG="v1.6.0-beta.0" RELEASE_DATE="2023-11-28" PROVIDER_ISSUES_DRY_RUN="true" make release-provider-issues-tool
// GITHUB_ISSUE_OPENER_TOKEN="fake" RELEASE_TAG="v1.6.0-rc.0" RELEASE_DATE="2023-11-28" PROVIDER_ISSUES_DRY_RUN="true" make release-provider-issues-tool
func main() {
githubToken, keySet := os.LookupEnv("GITHUB_ISSUE_OPENER_TOKEN")
if !keySet || githubToken == "" {
Expand Down Expand Up @@ -264,11 +266,11 @@ func getReleaseDetails() (releaseDetails, error) {
return releaseDetails{}, errors.New("release tag is a required. Refer to README.md in folder for more information")
}

// allow patterns like v1.7.0-beta.0
pattern := `^v\d+\.\d+\.\d+-beta\.\d+$`
// allow patterns like v1.7.0-alpha.0, v1.7.0-beta.0, v1.7.0-rc.0
pattern := `^v\d+\.\d+\.\d+-(alpha|beta|rc)\.\d+$`
match, err := regexp.MatchString(pattern, releaseSemVer)
if err != nil || !match {
return releaseDetails{}, errors.New("release tag must be in format `^v\\d+\\.\\d+\\.\\d+-beta\\.\\d+$` e.g. v1.7.0-beta.0")
return releaseDetails{}, errors.New("release tag must be in format `^v\\d+\\.\\d+\\.\\d+-(alpha|beta|rc)\\.\\d+$` e.g. v1.7.0-beta.0")
}

major, minor, patch := "", "", ""
Expand Down Expand Up @@ -296,14 +298,13 @@ func getReleaseDetails() (releaseDetails, error) {

majorMinorWithoutPrefixV := fmt.Sprintf("%s.%s", major, minor) // e.g. 1.7 . Note that there is no "v" in the majorMinor
releaseTag := fmt.Sprintf("v%s.%s.%s", major, minor, patch) // e.g. v1.7.0
betaTag := fmt.Sprintf("%s%s", releaseTag, "-beta.0") // e.g. v1.7.0-beta.0
releaseLink := fmt.Sprintf("https://github.com/kubernetes-sigs/cluster-api/tree/main/docs/release/releases/release-%s.md#timeline", majorMinorWithoutPrefixV)
releaseNotesLink := fmt.Sprintf("https://github.com/kubernetes-sigs/cluster-api/releases/tag/%s", betaTag)
releaseNotesLink := fmt.Sprintf("https://github.com/kubernetes-sigs/cluster-api/releases/tag/%s", releaseSemVer)

return releaseDetails{
ReleaseDate: formattedReleaseDate,
ReleaseTag: releaseTag,
BetaTag: betaTag,
PreReleaseTag: releaseSemVer,
ReleaseLink: releaseLink,
ReleaseNotesLink: releaseNotesLink,
}, nil
Expand Down Expand Up @@ -339,13 +340,13 @@ func formatDate(inputDate string) (string, error) {
func getIssueBody() *template.Template {
// do not indent the body
// indenting the body will result in the body being posted as a code snippet
issueBody, err := template.New("issue").Parse(`CAPI {{.BetaTag}} has been released and is ready for testing.
issueBody, err := template.New("issue").Parse(`CAPI {{.PreReleaseTag}} has been released and is ready for testing.
Looking forward to your feedback before {{.ReleaseTag}} release!

## For quick reference

<!-- body -->
- [CAPI {{.BetaTag}} release notes]({{.ReleaseNotesLink}})
- [CAPI {{.PreReleaseTag}} release notes]({{.ReleaseNotesLink}})
- [Shortcut to CAPI git issues](https://github.com/kubernetes-sigs/cluster-api/issues)

## Following are the planned dates for the upcoming releases
Expand All @@ -365,7 +366,7 @@ More details of the upcoming schedule can be seen at [CAPI {{.ReleaseTag}} relea

// getIssueTitle returns the issue title template.
func getIssueTitle() *template.Template {
issueTitle, err := template.New("title").Parse(`CAPI {{.BetaTag}} has been released and is ready for testing`)
issueTitle, err := template.New("title").Parse(`CAPI {{.PreReleaseTag}} has been released and is ready for testing`)
if err != nil {
panic(err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,38 +35,71 @@ func Test_GetReleaseDetails(t *testing.T) {
err string
}{
{
name: "Correct RELEASE_TAG and RELEASE_DATE are set",
name: "Correct RELEASE_TAG and RELEASE_DATE are set for alpha",
releaseTag: "v1.7.0-alpha.0",
releaseDate: "2024-04-16",
want: releaseDetails{
ReleaseDate: "Tuesday, 16th April 2024",
ReleaseTag: "v1.7.0",
PreReleaseTag: "v1.7.0-alpha.0",
ReleaseLink: "https://github.com/kubernetes-sigs/cluster-api/tree/main/docs/release/releases/release-1.7.md#timeline",
ReleaseNotesLink: "https://github.com/kubernetes-sigs/cluster-api/releases/tag/v1.7.0-alpha.0",
},
expectErr: false,
},
{
name: "Correct RELEASE_TAG and RELEASE_DATE are set for beta",
releaseTag: "v1.7.0-beta.0",
releaseDate: "2024-04-16",
want: releaseDetails{
ReleaseDate: "Tuesday, 16th April 2024",
ReleaseTag: "v1.7.0",
BetaTag: "v1.7.0-beta.0",
PreReleaseTag: "v1.7.0-beta.0",
ReleaseLink: "https://github.com/kubernetes-sigs/cluster-api/tree/main/docs/release/releases/release-1.7.md#timeline",
ReleaseNotesLink: "https://github.com/kubernetes-sigs/cluster-api/releases/tag/v1.7.0-beta.0",
},
expectErr: false,
},
{
name: "RELEASE_TAG is not in the format ^v\\d+\\.\\d+\\.\\d+-beta\\.\\d+$",
name: "Correct RELEASE_TAG and RELEASE_DATE are set for rc",
releaseTag: "v1.7.0-rc.0",
releaseDate: "2024-04-16",
want: releaseDetails{
ReleaseDate: "Tuesday, 16th April 2024",
ReleaseTag: "v1.7.0",
PreReleaseTag: "v1.7.0-rc.0",
ReleaseLink: "https://github.com/kubernetes-sigs/cluster-api/tree/main/docs/release/releases/release-1.7.md#timeline",
ReleaseNotesLink: "https://github.com/kubernetes-sigs/cluster-api/releases/tag/v1.7.0-rc.0",
},
expectErr: false,
},
{
name: "RELEASE_TAG is not in the correct format",
releaseTag: "v1.7.0.1",
releaseDate: "2024-04-16",
expectErr: true,
err: "release tag must be in format `^v\\d+\\.\\d+\\.\\d+-beta\\.\\d+$` e.g. v1.7.0-beta.0",
err: "release tag must be in format `^v\\d+\\.\\d+\\.\\d+-(alpha|beta|rc)\\.\\d+$` e.g. v1.7.0-beta.0",
},
{
name: "RELEASE_TAG does not have prefix 'v' in its semver",
releaseTag: "1.7.0-beta.0",
releaseDate: "2024-04-16",
expectErr: true,
err: "release tag must be in format `^v\\d+\\.\\d+\\.\\d+-beta\\.\\d+$` e.g. v1.7.0-beta.0",
err: "release tag must be in format `^v\\d+\\.\\d+\\.\\d+-(alpha|beta|rc)\\.\\d+$` e.g. v1.7.0-beta.0",
},
{
name: "RELEASE_TAG contains invalid Major.Minor.Patch SemVer",
releaseTag: "v1.x.0-beta.0",
releaseDate: "2024-04-16",
expectErr: true,
err: "release tag must be in format `^v\\d+\\.\\d+\\.\\d+-beta\\.\\d+$` e.g. v1.7.0-beta.0",
err: "release tag must be in format `^v\\d+\\.\\d+\\.\\d+-(alpha|beta|rc)\\.\\d+$` e.g. v1.7.0-beta.0",
},
{
name: "RELEASE_TAG contains unsupported pre-release type",
releaseTag: "v1.7.0-gamma.0",
releaseDate: "2024-04-16",
expectErr: true,
err: "release tag must be in format `^v\\d+\\.\\d+\\.\\d+-(alpha|beta|rc)\\.\\d+$` e.g. v1.7.0-beta.0",
},
{
name: "invalid yyyy-dd-mm RELEASE_DATE entered",
Expand Down Expand Up @@ -104,7 +137,7 @@ func Test_GetReleaseDetails(t *testing.T) {
} else {
g.Expect(got.ReleaseDate).To(Equal(tt.want.ReleaseDate))
g.Expect(got.ReleaseTag).To(Equal(tt.want.ReleaseTag))
g.Expect(got.BetaTag).To(Equal(tt.want.BetaTag))
g.Expect(got.PreReleaseTag).To(Equal(tt.want.PreReleaseTag))
g.Expect(got.ReleaseLink).To(Equal(tt.want.ReleaseLink))
}
})
Expand Down
57 changes: 51 additions & 6 deletions hack/tools/release/notes/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"fmt"
"log"
"os/exec"
"strings"

"github.com/blang/semver/v4"
"github.com/pkg/errors"
Expand All @@ -42,6 +43,11 @@ const (
alphaRelease = "ALPHA RELEASE"
betaRelease = "BETA RELEASE"
releaseCandidate = "RELEASE CANDIDATE"

// Pre-release type constants.
preReleaseAlpha = "alpha"
preReleaseBeta = "beta"
preReleaseRC = "rc"
)

func main() {
Expand Down Expand Up @@ -140,15 +146,15 @@ func releaseTypeFromNewTag(newTagConfig string) string {
return ""
}

// Only allow RC and beta releases. More types must be defined here.
// Only allow alpha, beta and rc releases. More types must be defined here.
// If a new type is not defined, no warning banner will be printed.
switch newTag.Pre[0].VersionStr {
case "rc":
return releaseCandidate
case "beta":
return betaRelease
case "alpha":
case preReleaseAlpha:
return alphaRelease
case preReleaseBeta:
return betaRelease
case preReleaseRC:
return releaseCandidate
}
return ""
}
Expand Down Expand Up @@ -187,6 +193,45 @@ func validateConfig(config *notesCmdConfig) error {
}
}

if config.previousReleaseVersion != "" {
if err := validatePreviousReleaseVersion(config.previousReleaseVersion); err != nil {
return err
}
}

return nil
}

func validatePreviousReleaseVersion(previousReleaseVersion string) error {
// Extract version string from ref format (e.g. "tags/v1.0.0-rc.1" -> "v1.0.0-rc.1")
if !strings.Contains(previousReleaseVersion, "/") {
return errors.New("--previous-release-version must be in ref format (e.g. tags/v1.0.0-rc.1)")
}

parts := strings.SplitN(previousReleaseVersion, "/", 2)
if len(parts) != 2 {
return errors.New("--previous-release-version must be in ref format (e.g. tags/v1.0.0-rc.1)")
}

versionStr := parts[1]

// Parse the version to check if it contains alpha, beta, or rc
version, err := semver.ParseTolerant(versionStr)
if err != nil {
return errors.Wrap(err, "invalid --previous-release-version, is not a valid semver")
}

// Check if the version has pre-release identifiers
if len(version.Pre) == 0 {
return errors.Errorf("--previous-release-version must contain '%s', '%s', or '%s' pre-release identifier", preReleaseAlpha, preReleaseBeta, preReleaseRC)
}

// Check if the first pre-release identifier is 'alpha', 'beta', or 'rc'
preReleaseType := version.Pre[0].VersionStr
if preReleaseType != preReleaseAlpha && preReleaseType != preReleaseBeta && preReleaseType != preReleaseRC {
return errors.Errorf("--previous-release-version must contain '%s', '%s', or '%s' pre-release identifier", preReleaseAlpha, preReleaseBeta, preReleaseRC)
}

return nil
}

Expand Down
77 changes: 76 additions & 1 deletion hack/tools/release/notes/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ limitations under the License.
package main

import (
"strings"
"testing"

"github.com/blang/semver/v4"
Expand Down Expand Up @@ -229,12 +230,86 @@ func Test_validateConfig(t *testing.T) {
},
wantErr: false,
},
{
name: "Invalid previousReleaseVersion without ref format",
args: &notesCmdConfig{
fromRef: "ref1/tags",
toRef: "ref2/tags",
newTag: "v1.0.0",
previousReleaseVersion: "v1.0.0-rc.0",
},
wantErr: true,
errorMessage: "--previous-release-version must be in ref format",
},
{
name: "Valid previousReleaseVersion with rc in ref format",
args: &notesCmdConfig{
fromRef: "ref1/tags",
toRef: "ref2/tags",
newTag: "v1.0.0",
previousReleaseVersion: "tags/v1.0.0-rc.0",
},
wantErr: false,
},
{
name: "Valid previousReleaseVersion with alpha in ref format",
args: &notesCmdConfig{
fromRef: "ref1/tags",
toRef: "ref2/tags",
newTag: "v1.0.0",
previousReleaseVersion: "tags/v1.0.0-alpha.1",
},
wantErr: false,
},
{
name: "Valid previousReleaseVersion with beta in ref format",
args: &notesCmdConfig{
fromRef: "ref1/tags",
toRef: "ref2/tags",
newTag: "v1.0.0",
previousReleaseVersion: "tags/v1.0.0-beta.1",
},
wantErr: false,
},
{
name: "Invalid previousReleaseVersion without pre-release in ref format",
args: &notesCmdConfig{
fromRef: "ref1/tags",
toRef: "ref2/tags",
newTag: "v1.0.0",
previousReleaseVersion: "tags/v1.0.0",
},
wantErr: true,
errorMessage: "--previous-release-version must contain 'alpha', 'beta', or 'rc' pre-release identifier",
},
{
name: "Invalid previousReleaseVersion with unsupported pre-release type",
args: &notesCmdConfig{
fromRef: "ref1/tags",
toRef: "ref2/tags",
newTag: "v1.0.0",
previousReleaseVersion: "tags/v1.0.0-dev.1",
},
wantErr: true,
errorMessage: "--previous-release-version must contain 'alpha', 'beta', or 'rc' pre-release identifier",
},
{
name: "Invalid previousReleaseVersion with invalid semver",
args: &notesCmdConfig{
fromRef: "ref1/tags",
toRef: "ref2/tags",
newTag: "v1.0.0",
previousReleaseVersion: "tags/invalid-version",
},
wantErr: true,
errorMessage: "invalid --previous-release-version, is not a valid semver",
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good to add test case for beta version as well

}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateConfig(tt.args)
if tt.wantErr {
if err == nil || err.Error() != tt.errorMessage {
if err == nil || !strings.Contains(err.Error(), tt.errorMessage) {
t.Errorf("expected error '%s', got '%v'", tt.errorMessage, err)
}
} else if err != nil {
Expand Down