ingress-nginx-helm/magefiles/steps/release.go

396 lines
13 KiB
Go
Raw Normal View History

/*
Copyright 2023 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.
*/
2023-09-18 00:36:30 +00:00
package steps
import (
"context"
"errors"
"fmt"
"os"
"regexp"
"strings"
"github.com/google/go-github/v48/github"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
"golang.org/x/oauth2"
"gopkg.in/yaml.v3"
2023-09-18 00:36:30 +00:00
utils "k8s.io/ingress-nginx/magefiles/utils"
)
type Release mg.Namespace
var (
INGRESS_ORG = "kubernetes" // the owner so we can test from forks
INGRESS_REPO = "ingress-nginx" // the repo to pull from
RELEASE_BRANCH = "main" // we only release from main
GITHUB_TOKEN string // the Google/gogithub lib needs an PAT to access the GitHub API
K8S_IO_ORG = "kubernetes" // the owner or organization for the k8s.io repo
K8S_IO_REPO = "k8s.io" // the repo that holds the images yaml for production promotion
INGRESS_REGISTRY = "registry.k8s.io" // Container registry for storage Ingress-nginx images
KUSTOMIZE_INSTALL_VERSION = "sigs.k8s.io/kustomize/kustomize/v4@v4.5.4" // static deploys needs kustomize to generate the template
)
// ingress-nginx releases start with a TAG then a cloudbuild, then a promotion through a PR, this the location of that PR
var (
IMAGES_YAML = "https://raw.githubusercontent.com/kubernetes/k8s.io/main/registry.k8s.io/images/k8s-staging-ingress-nginx/images.yaml"
ctx = context.Background() // Context used for GitHub Client
)
const (
INDEX_DOCS = "docs/deploy/index.md" // index.md has a version of the controller and needs to updated
CHANGELOG = "Changelog.md" // Name of the changelog
)
2023-09-18 00:36:30 +00:00
// init will set the GitHub token from the committers/releasers env var
func init() {
GITHUB_TOKEN = os.Getenv("GITHUB_TOKEN")
}
2023-09-18 00:36:30 +00:00
// Release Create a new release of ingress nginx controller
func (Release) NewRelease(version string) {
newRelease(version, "")
}
2023-09-18 00:36:30 +00:00
func (Release) NewReleaseFromOld(version, oldversion string) {
newRelease(version, oldversion)
}
2023-09-18 00:36:30 +00:00
func (Release) E2EDocs() {
e2edocs, err := utils.GenerateE2EDocs()
utils.CheckIfError(err, "error on template")
err = os.WriteFile("docs/e2e-tests.md", []byte(e2edocs), 644)
utils.CheckIfError(err, "Could not write new e2e test file ")
}
2023-09-18 00:36:30 +00:00
func newRelease(version, oldversion string) {
// newRelease := Release{}
// update ingress-nginx version
// This is the step that kicks all the release process
// it is already done, so it kicks off the gcloud build of the controller images
// mg.Deps(mg.F(Tag.BumpNginx, version))
tag, err := getIngressNGINXVersion()
2023-09-18 00:36:30 +00:00
utils.CheckIfError(err, "RELEASE Retrieving the current Ingress Nginx Version")
2023-09-18 00:36:30 +00:00
utils.Info("RELEASE Checking Current Version %s to New Version %s", tag, version)
// if the version were upgrading does not match the TAG file, lets update the TAG file
if tag[1:] != version {
2023-09-18 00:36:30 +00:00
utils.Warning("RELEASE Ingress Nginx TAG %s and new version %s do not match", tag, version)
mg.Deps(mg.F(Tag.BumpNginx, fmt.Sprintf("v%s", version)))
}
// update git controller tag controller-v$version
mg.Deps(mg.F(Tag.NewControllerTag, version))
// make release notes
2023-09-18 00:36:30 +00:00
releaseNotes, err := makeReleaseNotes(version, oldversion)
utils.CheckIfError(err, "RELEASE Creating Release Notes for version %s", version)
utils.Info("RELEASE Release Notes %s completed", releaseNotes.Version)
// update chart values.yaml new controller tag and image digest
releaseNotes.PreviousHelmChartVersion = currentChartVersion()
// controller tag
updateChartValue("controller.image.tag", fmt.Sprintf("v%s", releaseNotes.Version))
2023-09-18 00:36:30 +00:00
utils.Debug("releaseNotes.ControllerImages[0].Name %s", releaseNotes.ControllerImages[0].Name)
utils.Debug("releaseNotes.ControllerImages[1].Name %s", releaseNotes.ControllerImages[1].Name)
// controller digest
if releaseNotes.ControllerImages[0].Name == "ingress-nginx/controller" {
2023-09-18 00:36:30 +00:00
utils.Debug("Updating Chart Value %s with %s", "controller.image.digest", releaseNotes.ControllerImages[0].Digest)
updateChartValue("controller.image.digest", releaseNotes.ControllerImages[0].Digest)
}
// controller chroot digest
if releaseNotes.ControllerImages[1].Name == "ingress-nginx/controller-chroot" {
2023-09-18 00:36:30 +00:00
utils.Debug("Updating Chart Value %s with %s", "controller.image.digestChroot", releaseNotes.ControllerImages[1].Digest)
updateChartValue("controller.image.digestChroot", releaseNotes.ControllerImages[1].Digest)
}
// update helm chart app version
mg.Deps(mg.F(Helm.UpdateVersion, version))
releaseNotes.NewHelmChartVersion = currentChartVersion()
// update helm chart release notes
updateChartReleaseNotes(releaseNotes.HelmUpdates)
// Run helm docs update
2023-09-18 00:36:30 +00:00
utils.CheckIfError(runHelmDocs(), "Error Updating Helm Docs ")
2023-09-18 00:36:30 +00:00
releaseNotes.HelmTemplate()
// update static manifest
2023-09-18 00:36:30 +00:00
utils.CheckIfError(updateStaticManifest(), "Error Updating Static manifests")
////update e2e docs
2023-09-18 00:36:30 +00:00
mg.Deps(mg.F(Release.E2EDocs))
// update documentation with ingress-nginx version
2023-09-18 00:36:30 +00:00
utils.CheckIfError(updateIndexMD(releaseNotes.PreviousControllerVersion, releaseNotes.NewControllerVersion), "Error Updating %s", INDEX_DOCS)
// keeping these manual for now
// git commit TODO
// make Pull Request TODO
// make release TODO
// mg.Deps(mg.F(Release.CreateRelease, version))
}
// the index.md doc needs the controller version updated
func updateIndexMD(old, new string) error {
2023-09-18 00:36:30 +00:00
utils.Info("Updating Deploy docs with new version")
data, err := os.ReadFile(INDEX_DOCS)
2023-09-18 00:36:30 +00:00
utils.CheckIfError(err, "Could not read INDEX_DOCS file %s", INDEX_DOCS)
datString := string(data)
datString = strings.Replace(datString, old, new, -1)
err = os.WriteFile(INDEX_DOCS, []byte(datString), 644)
if err != nil {
2023-09-18 00:36:30 +00:00
utils.ErrorF("Could not write new %s %s", INDEX_DOCS, err)
return err
}
return nil
}
// The static deploy scripts use kustomize to generate them, this function ensures kustomize is installed
func installKustomize() error {
2023-09-18 00:36:30 +00:00
utils.Info("Install Kustomize")
g0 := sh.RunCmd("go")
// somewhere in your main code
err := g0("install", KUSTOMIZE_INSTALL_VERSION)
if err != nil {
return err
}
return nil
}
func updateStaticManifest() error {
2023-09-18 00:36:30 +00:00
utils.CheckIfError(installKustomize(), "error installing kustomize")
// hack/generate-deploy-scripts.sh
err := sh.RunV("./hack/generate-deploy-scripts.sh")
if err != nil {
return err
}
return nil
}
//// CreateRelease Creates a new GitHub Release
//func (Release) CreateRelease(name string) {
// releaser, err := gh_release.NewReleaser(INGRESS_ORG, INGRESS_REPO, GITHUB_TOKEN)
2023-09-18 00:36:30 +00:00
// utils.CheckIfError(err, "GitHub Release Client error")
// newRelease, err := releaser.Create(fmt.Sprintf("controller-%s", name))
2023-09-18 00:36:30 +00:00
// utils.CheckIfError(err, "Create release error")
// utils.Info("New Release: Tag %v, ID: %v", newRelease.TagName, newRelease.ID)
//}
// Returns a GitHub client ready for use
func githubClient() *github.Client {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: GITHUB_TOKEN},
)
oauthClient := oauth2.NewClient(ctx, ts)
return github.NewClient(oauthClient)
}
// LatestCommitLogs Retrieves the commit log between the latest two controller versions.
func (Release) LatestCommitLogs() {
2023-09-18 00:36:30 +00:00
commitLog := commitsBetweenTags("", "")
for i, s := range commitLog {
2023-09-18 00:36:30 +00:00
utils.Info("#%v Version %v", i, s)
}
}
2023-09-18 00:36:30 +00:00
func commitsBetweenTags(newversion, oldversion string) []string {
var newTag, oldTag string
tags := getAllControllerTags()
2023-09-18 00:36:30 +00:00
newTag, oldTag = tags[0], tags[1]
if newversion != "" {
newTag = newversion
}
if oldversion != "" {
oldTag = oldversion
}
utils.Info("Getting Commits between %v and %v", newTag, oldTag)
commitLog, err := git("log", "--full-history", "--pretty", "--oneline", fmt.Sprintf("%v..%v", oldTag, newTag))
if commitLog == "" {
2023-09-18 00:36:30 +00:00
utils.Warning("All Controller Tags is empty")
}
2023-09-18 00:36:30 +00:00
utils.CheckIfError(err, "Retrieving Commit log")
return strings.Split(commitLog, "\n")
}
// Generate Release Notes
func (Release) ReleaseNotes(newVersion string) error {
2023-09-18 00:36:30 +00:00
notes, err := makeReleaseNotes(newVersion, "")
utils.CheckIfError(err, "Creating Release Notes for version %s", newVersion)
utils.Info("Release Notes %s completed", notes.Version)
return nil
}
2023-09-18 00:36:30 +00:00
func makeReleaseNotes(newVersion, oldVersion string) (*utils.ReleaseNote, error) {
newReleaseNotes := utils.ReleaseNote{}
newReleaseNotes.Version = newVersion
allControllerTags := getAllControllerTags()
// new version
newReleaseNotes.NewControllerVersion = allControllerTags[0]
newControllerVersion := fmt.Sprintf("controller-v%s", newVersion)
// the newControllerVersion should match the latest tag
if newControllerVersion != allControllerTags[0] {
return nil, errors.New(fmt.Sprintf("Generating release new version %s didnt match the current latest tag %s", newControllerVersion, allControllerTags[0]))
}
// previous version
newReleaseNotes.PreviousControllerVersion = allControllerTags[1]
2023-09-18 00:36:30 +00:00
if oldVersion != "" {
newReleaseNotes.PreviousControllerVersion = oldVersion
}
2023-09-18 00:36:30 +00:00
utils.Info("New Version: %s Old Version: %s", newReleaseNotes.NewControllerVersion, newReleaseNotes.PreviousControllerVersion)
2023-09-18 00:36:30 +00:00
commits := commitsBetweenTags(newReleaseNotes.NewControllerVersion, newReleaseNotes.PreviousControllerVersion)
// dependency_updates
// all_updates
var allUpdates []string
var depUpdates []string
var helmUpdates []string
prRegex := regexp.MustCompile("\\(#\\d+\\)")
depBot := regexp.MustCompile("^(\\w){1,10} Bump ")
helmRegex := regexp.MustCompile("helm|chart")
for i, s := range commits {
// matches on PR
if prRegex.Match([]byte(s)) {
// matches a dependant bot update
if depBot.Match([]byte(s)) { //
2023-09-18 00:36:30 +00:00
utils.Debug("#%v DEPENDABOT %v", i, s)
u := strings.SplitN(s, " ", 2)
depUpdates = append(depUpdates, u[1])
} else { // add it to the all updates slice
2023-09-18 00:36:30 +00:00
utils.Debug("#%v ALL UPDATES %v", i, s)
u := strings.SplitN(s, " ", 2)
allUpdates = append(allUpdates, u[1])
// helm chart updates
if helmRegex.Match([]byte(s)) {
u := strings.SplitN(s, " ", 2)
helmUpdates = append(helmUpdates, u[1])
}
}
}
}
helmUpdates = append(helmUpdates, fmt.Sprintf("Update Ingress-Nginx version %s", newReleaseNotes.NewControllerVersion))
newReleaseNotes.Updates = allUpdates
newReleaseNotes.DepUpdates = depUpdates
newReleaseNotes.HelmUpdates = helmUpdates
// controller_image_digests
2023-09-18 00:36:30 +00:00
imagesYaml, err := utils.DownloadFile(IMAGES_YAML)
if err != nil {
2023-09-18 00:36:30 +00:00
utils.ErrorF("Could not download file %s : %s", IMAGES_YAML, err)
return nil, err
}
2023-09-18 00:36:30 +00:00
utils.Debug("%s", imagesYaml)
2023-09-18 00:36:30 +00:00
data := utils.ImageYamls{}
err = yaml.Unmarshal([]byte(imagesYaml), &data)
if err != nil {
2023-09-18 00:36:30 +00:00
utils.ErrorF("Could not unmarshal images yaml %s", err)
return nil, err
}
// controller
2023-09-18 00:36:30 +00:00
controllerDigest := utils.FindImageDigest(data, "controller", newVersion)
if len(controllerDigest) == 0 {
2023-09-18 00:36:30 +00:00
utils.ErrorF("Controller Digest could not be found")
return nil, errors.New("Controller digest could not be found")
}
2023-09-18 00:36:30 +00:00
controllerChrootDigest := utils.FindImageDigest(data, "controller-chroot", newVersion)
if len(controllerChrootDigest) == 0 {
2023-09-18 00:36:30 +00:00
utils.ErrorF("Controller Chroot Digest could not be found")
return nil, errors.New("Controller Chroot digest could not be found")
}
2023-09-18 00:36:30 +00:00
utils.Debug("Latest Controller Digest %v", controllerDigest)
utils.Debug("Latest Controller Chroot Digest %v", controllerChrootDigest)
c1 := utils.ControllerImage{
Digest: controllerDigest,
Registry: INGRESS_REGISTRY,
Name: "ingress-nginx/controller",
Tag: fmt.Sprintf("v%s", newReleaseNotes.Version),
}
2023-09-18 00:36:30 +00:00
c2 := utils.ControllerImage{
Digest: controllerChrootDigest,
Registry: INGRESS_REGISTRY,
Name: "ingress-nginx/controller-chroot",
Tag: fmt.Sprintf("v%s", newReleaseNotes.Version),
}
newReleaseNotes.ControllerImages = append(newReleaseNotes.ControllerImages, c1)
newReleaseNotes.ControllerImages = append(newReleaseNotes.ControllerImages, c2)
2023-09-18 00:36:30 +00:00
utils.Debug("New Release Controller Images %s %s", newReleaseNotes.ControllerImages[0].Digest, newReleaseNotes.ControllerImages[1].Digest)
2023-09-18 00:36:30 +00:00
if utils.DEBUG {
newReleaseNotes.PrintRelease()
}
// write it all out to the changelog file
2023-09-18 00:36:30 +00:00
newReleaseNotes.Template()
return &newReleaseNotes, nil
}
// Latest returns latest Github Release
func (Release) Latest() error {
r, _, err := latestRelease()
if err != nil {
2023-09-18 00:36:30 +00:00
utils.ErrorF("Latest Release error %s", err)
return err
}
2023-09-18 00:36:30 +00:00
utils.Info("Latest Release %v", r.String())
return nil
}
func (Release) ReleaseByTag(tag string) error {
r, _, err := releaseByTag(tag)
if err != nil {
2023-09-18 00:36:30 +00:00
utils.ErrorF("Release retrieve tag %s error %s", tag, err)
return err
}
2023-09-18 00:36:30 +00:00
utils.Info("Latest Release %v", r.String())
return nil
}
func releaseByTag(tag string) (*github.RepositoryRelease, *github.Response, error) {
ghClient := githubClient()
return ghClient.Repositories.GetReleaseByTag(ctx, INGRESS_ORG, INGRESS_REPO, tag)
}
func latestRelease() (*github.RepositoryRelease, *github.Response, error) {
ghClient := githubClient()
return ghClient.Repositories.GetLatestRelease(ctx, INGRESS_ORG, INGRESS_REPO)
}