tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/githubRelease.go (269 lines of code) (raw):

package eksGoRelease import ( "context" "fmt" "strconv" "strings" "time" "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/constants" "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/git" "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/github" "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/logger" "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/prManager" "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/retrier" ) const ( minorReleaseBranchFmt = "eks-%s" filePathFmt = "%s/%s/%s" patchesPathFmt = "%s/%s/patches/%s" rpmSourcePathFmt = "%s/%s/rpmbuild/SOURCES/%s" specPathFmt = "%s/%s/rpmbuild/SPECS/%s" readmeFmtPath = "tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/versionReadmeFmt.txt" newReleaseFile = "tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/newRelease.txt" fedoraFile = "fedora.go" gdbinitFile = "golang-gdbinit" goSpecFile = "golang.spec" ) func generateVersionReadme(readmeFmt string, r *Release) string { /* Format generated for the readme follows: * ---------------------------------------- * # EKS Golang <title> * * Current Release: `<curRelease>` * * Tracking Tag: `<trackTag>` * * ### Artifacts: * |Arch|Artifact|sha| * |:---:|:---:|:---:| * |noarch|[%s](%s)|[%s](%s)| * |x86_64|[%s](%s)|[%s](%s)| * |aarch64|[%s](%s)|[%s](%s)| * |arm64.tar.gz|[%s](%s)|[%s](%s)| * |amd64.tar.gz|[%s](%s)|[%s](%s)| * * ### ARM64 Builds * [![Build status](<armBuild>)](https://prow.eks.amazonaws.com/?repo=aws%2Feks-distro-build-tooling&type=postsubmit) * * ### AMD64 Builds * [![Build status](<amdBuild>)](https://prow.eks.amazonaws.com/?repo=aws%2Feks-distro-build-tooling&type=postsubmit) * * ### Patches * The patches in `./patches` include relevant utility fixes for go `<patch>`. * * ### Spec * The RPM spec file in `./rpmbuild/SPECS` is sourced from the go <fSpec> SRPM available on Fedora, and modified to include the relevant patches and build the `go<sSpec>` source. * */ eksGoArches := [...]string{"noarch", "x86_64", "aarch64", "arm64", "amd64"} artifactTable := "" for _, a := range eksGoArches { artifact, sha, url := r.EksGoArtifacts(a) artifactTable = artifactTable + fmt.Sprintf("|%s|[%s](%s)|[%s](%s)|\n", a, artifact, fmt.Sprintf("%s/%s", url, artifact), sha, fmt.Sprintf("%s/%s", url, sha)) } title := r.GoMinorVersion() curRelease := r.ReleaseNumber() trackTag := r.GoFullVersion() armBuild := r.EksGoArmBuild() amdBuild := r.EksGoAmdBuild() patch := r.GoMinorVersion() fSpec := r.GoMinorVersion() sSpec := r.GoFullVersion() return fmt.Sprintf(readmeFmt, title, curRelease, trackTag, artifactTable, armBuild, amdBuild, patch, fSpec, sSpec) } // Update golang/go/<VERSION>/README.md func updateVersionReadme(gClient git.Client, r *Release) error { readmePath := fmt.Sprintf(filePathFmt, constants.EksGoProjectPath, r.GoMinorVersion(), constants.Readme) readmeFmt, err := gClient.ReadFile(readmeFmtPath) if err != nil { logger.Error(err, "Reading version README fmt file") return err } readmeContent := generateVersionReadme(readmeFmt, r) logger.V(4).Info("Update version README.md", "path", readmePath) logger.V(6).Info("Update version README.md", "content", readmeContent) if err := gClient.ModifyFile(readmePath, []byte(readmeContent)); err != nil { return err } if err := gClient.Add(readmePath); err != nil { return err } return nil } func updateSupportedVersionsProjectReadme(fc *string, r *Release) string { slO := "## Supported Versions\nEKS currently supports the following Golang versions:" slN := fmt.Sprintf("## Supported Versions\nEKS currently supports the following Golang versions:\n- [`v%s`](./%s/GIT_TAG)", r.GoMinorVersion(), r.GoMinorVersion()) *fc = strings.Replace(*fc, slO, slN, 1) // EKS Go supports n-1 versions outside upstream. Ie if upstream supprot 1.22, 1.21, eks go supports 1.20 dv := fmt.Sprintf("%d.%d", r.Major, r.Minor-3) slO = fmt.Sprintf("\n- [`v%s`](./%s/GIT_TAG)", dv, dv) slN = "\n" *fc = strings.Replace(*fc, slO, slN, 1) return *fc } func updateDeprecatedVersionsProjectReadme(fc *string, r *Release) string { // EKS Go supports n-1 versions outside upstream. Ie if upstream supprot 1.22, 1.21, eks go supports 1.20 // dv represents this as the most recent deprecated version dv := fmt.Sprintf("%d.%d", r.Major, r.Minor-3) // EKS Go supports n-1 versions outside upstream. Ie if upstream supprot 1.22, 1.21, eks go supports 1.20 // lsv represents this as the last supported version lsv := fmt.Sprintf("%d.%d", r.Major, r.Minor-2) dlO := "## Deprecated Versions" dlN := fmt.Sprintf("## Deprecated Versions\n- [`v%s`](./%s/GIT_TAG)", dv, dv) *fc = strings.Replace(*fc, dlO, dlN, 1) dnO := "**Due to the increased security risk this poses, it is HIGHLY recommended that users of `EKS-GO v1.15 - v1.17` update to a supported version of EKS-Go (v1.18+) as soon as possible.**" dnN := fmt.Sprintf("**Due to the increased security risk this poses, it is HIGHLY recommended that users of `EKS-GO v1.15 - v%s` update to a supported version of EKS-Go (v%s+) as soon as possible.**", dv, lsv) *fc = strings.Replace(*fc, dnO, dnN, 1) return *fc } // Update projects/golang/go/README.md func updateProjectReadme(gClient git.Client, r *Release) error { readmePath := fmt.Sprintf("%s/%s", constants.EksGoProjectPath, constants.Readme) readmeContent, err := gClient.ReadFile(readmePath) if err != nil { logger.Error(err, "Reading project README file") return err } readmeContent = updateSupportedVersionsProjectReadme(&readmeContent, r) readmeContent = updateDeprecatedVersionsProjectReadme(&readmeContent, r) logger.V(4).Info("Update version README.md", "path", readmePath) logger.V(6).Info("Update version README.md", "content", readmeContent) if err := gClient.ModifyFile(readmePath, []byte(readmeContent)); err != nil { return err } if err := gClient.Add(readmePath); err != nil { return err } return nil } func bumpRelease(gClient git.Client, r *Release) error { // Get Current EKS Go Release Version from repo and increment releasePath := fmt.Sprintf(filePathFmt, constants.EksGoProjectPath, r.GoMinorVersion(), constants.Release) content, err := gClient.ReadFile(releasePath) if err != nil { if !strings.Contains(err.Error(), "file not found") { logger.Error(err, "Reading file", "file", releasePath) return err } r.Release = 0 } else { // Check if there is a new line character at the end of the file, if so take all but the newline if content[len(content)-1:] == "\n" { content = content[0 : len(content)-1] } cr, err := strconv.Atoi(content) if err != nil { logger.Error(err, "Converting current release to int") return err } // Increment release r.Release = cr + 1 } logger.V(4).Info("release bumped to", "release", r.Release) return nil } func updateRelease(gClient git.Client, r *Release) error { logger.V(4).Info("gClient", "client", gClient) // update RELEASE releasePath := fmt.Sprintf(filePathFmt, constants.EksGoProjectPath, r.GoMinorVersion(), constants.Release) releaseContent := fmt.Sprintf("%d", r.ReleaseNumber()) logger.V(4).Info("Update RELEASE", "path", releasePath, "content", releaseContent) if err := gClient.ModifyFile(releasePath, []byte(releaseContent)); err != nil { if !strings.Contains(err.Error(), "file not found") { return err } releaseContent = fmt.Sprintf("%d", 0) if err := gClient.CreateFile(releasePath, []byte(releaseContent)); err != nil { return err } } if err := gClient.Add(releasePath); err != nil { logger.Error(err, "git add", "file", releasePath) return err } return nil } func updateGitTag(gClient git.Client, r *Release) error { // update GIT_TAG gittagPath := fmt.Sprintf(filePathFmt, constants.EksGoProjectPath, r.GoMinorVersion(), constants.GitTag) gittagContent := fmt.Sprintf("go%s", r.GoFullVersion()) logger.V(4).Info("Update GIT_TAG", "path", gittagPath, "content", gittagContent) if err := gClient.ModifyFile(gittagPath, []byte(gittagContent)); err != nil { return err } if err := gClient.Add(gittagPath); err != nil { logger.Error(err, "git add", "file", gittagPath) return err } return nil } func updateGoSpecPatchVersion(fc *string, r *Release) string { gpO := fmt.Sprintf("%%global go_patch %d", r.PatchVersion()-1) gpN := fmt.Sprintf("%%global go_patch %d", r.PatchVersion()) return strings.Replace(*fc, gpO, gpN, 1) } // TODO: Fix logic to apply previous patches, cherry pick fix, and create patch file func createPatchFile(ctx context.Context, r *Release, gClient git.Client, golangClient git.Client, commit string) error { // Attempt patch generation if it fails, skip updating gospec with new patch number // Clone https://github.com/golang/go if err := golangClient.Clone(ctx); err != nil { logger.Error(err, "git clone", "repo", constants.GoRepoUrl) return err } if err := golangClient.Branch(r.GoReleaseBranch()); err != nil { logger.Error(err, "git branch", "branch name", r.GoReleaseBranch(), "repo", constants.GoRepoUrl, "client", golangClient) return err } // TODO: Apply patches current patches in <Version>/patches/ // TODO: cherry pick commit string // TODO: Format-patch the last commit which will be the chrrypick commit // TODO: Copy patch file to EKS Go patch folder return nil } func updateGoSpec(gClient git.Client, r *Release) error { // update golang.spec goSpecPath := fmt.Sprintf(specPathFmt, constants.EksGoProjectPath, r.GoMinorVersion(), goSpecFile) goSpecContent, err := gClient.ReadFile(goSpecPath) if err != nil { logger.Error(err, "Reading spec.golang", "file", goSpecPath) return err } goSpecContent = updateGoSpecPatchVersion(&goSpecContent, r) logger.V(4).Info("Update golang.spec", "path", goSpecPath, "content", goSpecContent) if err := gClient.ModifyFile(goSpecPath, []byte(goSpecContent)); err != nil { return err } if err := gClient.Add(goSpecPath); err != nil { logger.Error(err, "git add", "file", goSpecPath) return err } return nil } func addTempFilesForNewMinorVersion(gClient git.Client, r *Release) error { // Add golang.spec specFilePath := fmt.Sprintf(specPathFmt, constants.EksGoProjectPath, r.GoMinorVersion(), goSpecFile) rf, err := gClient.ReadFile(newReleaseFile) if err != nil { logger.Error(err, "Reading newRelease.txt file") return err } newReleaseContent := rf if err := gClient.CreateFile(specFilePath, []byte(newReleaseContent)); err != nil { logger.Error(err, "Adding fedora file", "path", specFilePath) return err } if err := gClient.Add(specFilePath); err != nil { logger.Error(err, "git add", "file", specFilePath) return err } // Add golang-gdbinit gdbinitFilePath := fmt.Sprintf(rpmSourcePathFmt, constants.EksGoProjectPath, r.GoMinorVersion(), gdbinitFile) if err := gClient.CreateFile(gdbinitFilePath, []byte(newReleaseContent)); err != nil { logger.Error(err, "Adding fedora file", "path", gdbinitFilePath) return err } if err := gClient.Add(gdbinitFilePath); err != nil { logger.Error(err, "git add", "file", gdbinitFilePath) return err } // Add fedora.go fedoraFilePath := fmt.Sprintf(rpmSourcePathFmt, constants.EksGoProjectPath, r.GoMinorVersion(), fedoraFile) if err := gClient.CreateFile(fedoraFilePath, []byte(newReleaseContent)); err != nil { logger.Error(err, "Adding fedora file", "path", fedoraFilePath) return err } if err := gClient.Add(fedoraFilePath); err != nil { logger.Error(err, "git add", "file", fedoraFilePath) return err } return nil } func createReleasePR(ctx context.Context, dryrun bool, r *Release, ghUser github.GitHubUser, gClient git.Client, prSubject, prDescription, commitMsg, commitBranch string) error { if dryrun { logger.V(3).Info("running in dryrun mode no pr created") return nil } retrier := retrier.New(time.Second*380, retrier.WithBackoffFactor(1.5), retrier.WithMaxRetries(15, time.Second*30)) githubClient, err := github.NewClient(ctx, ghUser.Token()) if err != nil { return fmt.Errorf("setting up Github client: %v", err) } // Commit files // set up PR Creator handler prmOpts := &prManager.Opts{ SourceOwner: ghUser.User(), SourceRepo: constants.EksdBuildToolingRepoName, PrRepo: constants.EksdBuildToolingRepoName, PrRepoOwner: constants.AwsOrgName, } prm := prManager.New(retrier, githubClient, prmOpts) prOpts := &prManager.CreatePrOpts{ CommitBranch: commitBranch, BaseBranch: "main", AuthorName: ghUser.User(), AuthorEmail: ghUser.Email(), PrSubject: prSubject, PrBranch: "main", PrDescription: prDescription, } if err := gClient.Commit(commitMsg); err != nil { logger.Error(err, "git commit", "message", commitMsg) return err } // Push to forked repository if err := gClient.Push(ctx); err != nil { logger.Error(err, "git push") return err } prUrl, err := prm.CreatePr(ctx, prOpts) if err != nil { // This shouldn't be an breaking error at this point the PR is not open but the changes // have been pushed and can be created manually. logger.Error(err, "github client create pr failed. Create PR manually from github webclient", "create pr opts", prOpts) prUrl = "" } logger.V(3).Info("Update EKS Go Version", "EKS Go Version", r.EksGoReleaseVersion(), "PR", prUrl) return nil }