diff --git a/.gitignore b/.gitignore index e63303937..6ba48e487 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ eks-distro-base/check-update/ eks-distro-base/*-pushed eks-distro-base/make-tests/actual/ builder-base/tmp/ +tools/eksDistroBuildToolingOpsTools/bin + diff --git a/projects/golang/go/README.md b/projects/golang/go/README.md index 368b80529..ad528fbbd 100644 --- a/projects/golang/go/README.md +++ b/projects/golang/go/README.md @@ -8,24 +8,47 @@ and executing the Kubernetes conformance tests. EKS Go RPMs are publicly available; see [Access EKS Go Artifacts](#access-eks-go-artifacts). +## New Minor Releases and Patch Releases +### Adding Minor Version +1. Build the CLI tool by opening [eksDistroBuildToolingOpsTools](/tools/eksDistroBuildToolingOpsTools/) and run `make build-eksGoRelease` +2. Run `bin/$(GO_OS)/$(GO_ARCH)/eksGoRelease new -u -e --eksGoReleases=` + 1. example `bin/darwing/amd64/eksGoRelease new -u rcrozean -e rcrozean@amazon.com --eksGoReleases=1.22.0` +3. Check the PRs and update changelogs before requesting approval. +4. Once the PRs have been merged and post-submits finish and pass run `bin/$(GO_OS)/$(GO_ARCH)/eksGoRelease release -u -e --eksGoReleases=` to bump the release files and publish the new artifacts + 1. example `bin/darwing/amd64/eksGoRelease release -u rcrozean -e rcrozean@amazon.com --eksGoReleases=1.20.11,1.21.4` + +### Updating Upstream Supported Patch Versions +1. Build the CLI tool by opening [eksDistroBuildToolingOpsTools](/tools/eksDistroBuildToolingOpsTools/) and run `make build-eksGoRelease` +2. Run `bin/$(GO_OS)/$(GO_ARCH)/eksGoRelease update -u -e --eksGoReleases=` + 1. example `bin/darwing/amd64/eksGoRelease update -u rcrozean -e rcrozean@amazon.com --eksGoReleases=1.20.11,1.21.4` +3. Check the PRs and update changelogs before requesting approval. +4. Once the PRs have been merged and post-submits finish and pass run `bin/$(GO_OS)/$(GO_ARCH)/eksGoRelease release -u -e --eksGoReleases=` to bump the release files and publish the new artifacts + 1. example `bin/darwing/amd64/eksGoRelease release -u rcrozean -e rcrozean@amazon.com --eksGoReleases=1.20.11,1.21.4` + +### Updating Upstream Unsupported Patch Versions +Follow [Updating Upstream Supported Patch Versions](#updating-upstream-supported-patch-versions) steps. There is a WIP cli command located at [patchEksGo.go](../../../tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/patchEksGo.go) and the tooling for the command [createPatch.go](../../../tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/createPatch.go). + +TODO for WIP: +- Add `git cherry-pick`, `git am`, and `git format-patch` to [eksDistroBuildToolingOpsTools/pkg/git](/tools/eksDistroBuildToolingOpsTools/pkg/git) or to [go-git](https://github.com/go-git/go-git/blob/master/COMPATIBILITY.md) +- Add logic to apply patches, cherry pick [upstream's](https://github.com/golang/go) fix, and format patch to [createPatch.go](/tools/eksDistroBuildToolingOpsTools/pkg/eksGoReleases/createPatch.go) ## Supported Versions EKS currently supports the following Golang versions: -- [`v1.19`](./1.19/GIT_TAG) -- [`v1.20`](./1.20/GIT_TAG) - [`v1.21`](./1.21/GIT_TAG) +- [`v1.20`](./1.20/GIT_TAG) +- [`v1.19`](./1.19/GIT_TAG) ## Deprecated Versions -- [`v1.15`](./1.15/GIT_TAG) -- [`v1.16`](./1.16/GIT_TAG) -- [`v1.17`](./1.16/GIT_TAG) - [`v1.18`](./1.18/GIT_TAG) +- [`v1.17`](./1.16/GIT_TAG) +- [`v1.16`](./1.16/GIT_TAG) +- [`v1.15`](./1.15/GIT_TAG) For versions of `EKS-Go` EKS Distro has [discontinued support](#deprecated-versions) for, there are no plans for removing artifacts from the public ECR. EKS-Distro won’t be backporting any upcoming golang security fixes for these versions. -**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.** +**Due to the increased security risk this poses, it is HIGHLY recommended that users of `EKS-GO v1.15 - v1.18` update to a supported version of EKS-Go (v1.19+) as soon as possible.** ## Upstream Patches diff --git a/tools/eksDistroBuildToolingOpsTools/Makefile b/tools/eksDistroBuildToolingOpsTools/Makefile index 8615fea54..5a37fa0d6 100644 --- a/tools/eksDistroBuildToolingOpsTools/Makefile +++ b/tools/eksDistroBuildToolingOpsTools/Makefile @@ -20,6 +20,7 @@ EKS_GO_TOOLS_BINARY_NAME=eksGoTools CONSUMER_UPDATER_BINARY_NAME=consumerUpdater EKS_PROW_PLUGIN_BINARY_NAME=eksDistroOpsProwPlugin EKS_DISTRO_PROW_PLUGIN_ECR_NAME=eks-distro-ops-tool +GOLANG_RELEASE_BINARY_NAME=eksGoRelease .PHONY: build-eks-go-tools build-eks-go-tools: @@ -33,6 +34,10 @@ build-consumerUpdater: unit-test lint build-binary-eksDistroOpsProwPlugin: ./scripts/create_binaries.sh ${GOLANG_VERSION} ${GO_OS} ${GO_ARCH} ${GIT_VERSION} ${EKS_PROW_PLUGIN_BINARY_NAME} +.PHONY: build-eksGoRelease +build-eksGoRelease: unit-test + CGO_ENABLED=0 GOOS=$(GO_OS) GOARCH=$(GO_ARCH) go build -ldflags "-X github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/version.gitVersion=$(GIT_VERSION) -s -w -extldflags -static" -o bin/$(GO_OS)/$(GO_ARCH)/$(GOLANG_RELEASE_BINARY_NAME) github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/cmd/$(GOLANG_RELEASE_BINARY_NAME) + .PHONY: build-eksDistroOpsProwPlugin build-eksDistroOpsProwPlugin: build-binary-eksDistroOpsProwPlugin local-images-eksDistroOpsProwPlugin diff --git a/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/README.md b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/README.md new file mode 100644 index 000000000..4dbeef8cf --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/README.md @@ -0,0 +1,29 @@ +## EKS Go Release Tool +The EKS Go Release Tool is intended as a home for all automation used to release, update, and patch EKS Go with new releases. +It is intended to eventually replace eksGoTool. + +### Using the Tool +use `eksGoRelease --help` for up-to-date usage + +The tool has an `release` command which creates new minor version release structure and files. +The tool has an `update` command which updates upstream supported versions for new patch releases. + +The `release` command accepts a EKS Go release version via the flag `eksGoRelease`. +For example to release a new version of eksGo `1.21.0` +you would run the following: + +```shell +eksGoRelease release --eksGoReleases=1.21.0 +``` + +The `update` command accepts a comma-seperated list of EKS Go release versions via the flag `eksGoRelease`. +Updates are run for each given release version. For example, to run updaters for the EKS Go release `1.20.2` & `1.19.3`, +you would run the following: + +```shell +eksGoRelease update --eksGoReleases=1.20.2,1.19.3 +``` + +### Building the Tool +To build the Consumer Updater binary, run the build make target `make build-eksGoRelease` +from the root of the Ops Tool. This will produce a binary in `tools/eksDistroBuildToolingOpsTools/bin/$GOOS/$GOARCH/eksGoRelease`. diff --git a/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/newMinorEksGo.go b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/newMinorEksGo.go new file mode 100644 index 000000000..93dee55e6 --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/newMinorEksGo.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease" +) + +var newCmd = &cobra.Command{ + Use: "new", + Short: "Release a new minor version of EKS Go", + Long: "Tool to release a new minor version of EKS Go", + RunE: func(cmd *cobra.Command, args []string) error { + var eksGoReleases []*eksGoRelease.Release + for _, v := range viper.GetStringSlice(eksGoReleasesFlag) { + r, err := eksGoRelease.NewEksGoReleaseObject(v) + if err != nil { + return err + } + eksGoReleases = append(eksGoReleases, r) + } + + for _, r := range eksGoReleases { + err := eksGoRelease.NewMinorRelease(cmd.Context(), r, viper.GetBool(dryrunFlag), viper.GetString(emailFlag), viper.GetString(userFlag)) + if err != nil { + return fmt.Errorf("you have failed this automation: %w", err) + } + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(newCmd) +} diff --git a/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/patchEksGo.go b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/patchEksGo.go new file mode 100644 index 000000000..39dbb04f1 --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/patchEksGo.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease" +) + +var backportCmd = &cobra.Command{ + Use: "patch", + Short: "Cherrypick a patch to versions of EKS Go", + Long: "Tool to create PR for updaing EKS Go versions that require a patch applied", + RunE: func(cmd *cobra.Command, args []string) error { + var eksGoReleases []*eksGoRelease.Release + for _, v := range viper.GetStringSlice(eksGoReleasesFlag) { + r, err := eksGoRelease.NewEksGoReleaseObject(v) + if err != nil { + return err + } + eksGoReleases = append(eksGoReleases, r) + } + + for _, r := range eksGoReleases { + err := eksGoRelease.BackportToRelease(cmd.Context(), r, viper.GetBool(dryrunFlag), "CVE", "HASH", viper.GetString(emailFlag), viper.GetString(userFlag)) + if err != nil { + return fmt.Errorf("you have failed this automation: %w", err) + } + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(backportCmd) +} diff --git a/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/release.go b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/release.go new file mode 100644 index 000000000..f446a1338 --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/release.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease" +) + +var releaseCmd = &cobra.Command{ + Use: "release", + Short: "Release EKS Go", + Long: "Tool to create PRs for releasing EKS Go versions", + RunE: func(cmd *cobra.Command, args []string) error { + var eksGoReleases []*eksGoRelease.Release + for _, v := range viper.GetStringSlice(eksGoReleasesFlag) { + r, err := eksGoRelease.NewEksGoReleaseObject(v) + if err != nil { + return err + } + eksGoReleases = append(eksGoReleases, r) + } + + for _, r := range eksGoReleases { + err := eksGoRelease.ReleaseArtifacts(cmd.Context(), r, viper.GetBool(dryrunFlag), viper.GetString(emailFlag), viper.GetString(userFlag)) + if err != nil { + return fmt.Errorf("you have failed this automation: %w", err) + } + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(releaseCmd) +} diff --git a/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/root.go b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/root.go new file mode 100644 index 000000000..8bd33817b --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/root.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "context" + "fmt" + "log" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/logger" +) + +const ( + eksGoReleasesFlag = "eksGoReleases" + dryrunFlag = "dryrun" + userFlag = "user" + emailFlag = "email" +) + +var ( + rootCmd = &cobra.Command{ + Use: "eksGoRelease", + Short: "Amazon EKS Go Release and automation commands", + Long: `Tools for updating and releasing EKS Go`, + PersistentPreRun: rootPersistentPreRun, + } +) + +func init() { + rootCmd.PersistentFlags().IntP("verbosity", "v", 0, "Set the log level verbosity") + rootCmd.PersistentFlags().StringSlice(eksGoReleasesFlag, []string{}, "EKS Go releases to update") + rootCmd.PersistentFlags().BoolP(dryrunFlag, "d", false, "run without creating PR") + rootCmd.PersistentFlags().StringP(emailFlag, "e", "", "github email for git functions") + rootCmd.PersistentFlags().StringP(userFlag, "u", "", "github username for git functions") + + // Bind config flags to viper + if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { + log.Fatalf("failed to bind persistent flags for root: %v", err) + } +} + +func rootPersistentPreRun(cmd *cobra.Command, args []string) { + if err := initLogger(); err != nil { + log.Fatal(err) + } +} + +func initLogger() error { + if err := logger.InitZap(viper.GetInt("verbosity")); err != nil { + return fmt.Errorf("failed init zap logger in root command: %v", err) + } + + return nil +} + +func Execute() error { + return rootCmd.ExecuteContext(context.Background()) +} diff --git a/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/updateEksGo.go b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/updateEksGo.go new file mode 100644 index 000000000..7472cbaa3 --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd/updateEksGo.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease" +) + +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Update new patch versions of EKS Go", + Long: "Tool to create PR for updaing EKS Go versions supported by upstream when a patch version is released", + RunE: func(cmd *cobra.Command, args []string) error { + var eksGoReleases []*eksGoRelease.Release + for _, v := range viper.GetStringSlice(eksGoReleasesFlag) { + r, err := eksGoRelease.NewEksGoReleaseObject(v) + if err != nil { + return err + } + eksGoReleases = append(eksGoReleases, r) + } + + for _, r := range eksGoReleases { + err := eksGoRelease.UpdateVersion(cmd.Context(), r, viper.GetBool(dryrunFlag), viper.GetString(emailFlag), viper.GetString(userFlag)) + if err != nil { + return fmt.Errorf("you have failed this automation: %w", err) + } + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(updateCmd) +} diff --git a/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/main.go b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/main.go new file mode 100644 index 000000000..eb3b2bab6 --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "os" + "os/signal" + "syscall" + + "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/cmd/eksGoRelease/cmd" +) + +func main() { + sigChannel := make(chan os.Signal, 1) + signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-sigChannel + os.Exit(-1) + }() + if cmd.Execute() == nil { + os.Exit(0) + } + os.Exit(-1) +} diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/constants/constants.go b/tools/eksDistroBuildToolingOpsTools/pkg/constants/constants.go index 1bc03396c..d5fbe8e97 100644 --- a/tools/eksDistroBuildToolingOpsTools/pkg/constants/constants.go +++ b/tools/eksDistroBuildToolingOpsTools/pkg/constants/constants.go @@ -1,14 +1,30 @@ package constants const ( - AwsOrgName = "aws" - EksdBuildToolingRepoName = "eks-distro-build-tooling" - EksDistroBotName = "eks-distro-bot" - EksDistroPrBotName = "eks-distro-pr-bot" + AwsOrgName = "aws" + EksdBuildToolingRepoName = "eks-distro-build-tooling" + EksDistroBotName = "eks-distro-bot" + EksDistroPrBotName = "eks-distro-pr-bot" + OwnerWriteallReadOctal = 0644 + SemverRegex = `[0-9]+\.[0-9]+\.[0-9]+` + AllowAllFailRespTemplate = "@%s only [%s](https://github.com/orgs/%s/people) org members may request may trigger automated issues. You can still create the issue manually." + GitTag = "GIT_TAG" + Release = "RELEASE" + Readme = "README.md" + + // Github Constants + GolangOrgName = "golang" + GoRepoName = "go" + GoRepoUrl = "https://github.com/golang/go.git" + + // EKS Go Constants + EksGoRepoUrl = "https://github.com/%s/eks-distro-build-tooling.git" + EksGoProjectPath = "projects/golang/go" + EksGoAmdBuildUrl = "https://prow.eks.amazonaws.com/badge.svg?jobs=golang-%d-%d-tooling-postsubmit" + EksGoArmBuildUrl = "https://prow.eks.amazonaws.com/badge.svg?jobs=golang-%d-%d-ARM64-PROD-tooling-postsubmit" EksGoSupportedVersionsPath = "projects/golang/go/MAINTAINED_EOL_VERSIONS" - GolangOrgName = "golang" - GoRepoName = "go" - OwnerWriteallReadOctal = 0644 - SemverRegex = `[0-9]+\.[0-9]+\.[0-9]+` - AllowAllFailRespTemplate = "@%s only [%s](https://github.com/orgs/%s/people) org members may request may trigger automated issues. You can still create the issue manually." + EksGoArtifactUrl = "https://distro.eks.amazonaws.com/golang-go%d.%d.%d/releases/%d/%s/%s/%s" + EksGoTargzArtifactFmt = "go%d.%d.%d.linux-%s.tar.gz" + EksGoRpmArtifactFmt = "golang-%d.%d.%d-%d.amzn2.eks.%s.rpm" + EksGoNoarchRpmArtifactFmt = "golang-src-%d.%d.%d-%d.amzn2.eks.%s.rpm" ) diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/createPatch.go b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/createPatch.go new file mode 100644 index 000000000..600a7aa07 --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/createPatch.go @@ -0,0 +1,89 @@ +package eksGoRelease + +import ( + "context" + "fmt" + + "github.com/go-git/go-git/v5/plumbing/transport/http" + + "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" +) + +const ( + backportCommitMsgFmt = "Update EKS Go version %s files" + backportPRDescriptionFailureFmt = "This PR failed create a patch for %s to EKS Go Patch Version: %s\n The patch will need to be manually created from commit: %s\n\n/hold\n\nSPEC FILE STILL NEEDS THE '%%changelog' UPDATED\nPLEASE UPDATE WITH THE FOLLOWING FORMAT\n```\n* Wed Sep 06 2023 Cameron Rozean - 1.20.8-1\n- Patch CVE- in EKS Go version 1.20.8\n```" + backportPRDescriptionSuccessFmt = "This PR created a patch for %s from %s to EKS Go Patch Version: %s\n\n/hold\n\nSPEC FILE STILL NEEDS THE '%%changelog' UPDATED\nPLEASE UPDATE WITH THE FOLLOWING FORMAT\n```\n* Wed Sep 06 2023 Cameron Rozean - 1.20.8-1\n- Patch CVE- in EKS Go version 1.20.8\n```" + backportPRSubjectFmt = "Patch %s to EKS Go %s" +) + +// BackportPatchVersion is for updating the files in https://github.com/aws/eks-distro-build-tooling/golang/go for golang versions no longer maintained by upstream. +func BackportToRelease(ctx context.Context, r *Release, dryrun bool, cve, commit, email, user string) error { + // Setup Git Clients + token, err := github.GetGithubToken() + if err != nil { + logger.V(4).Error(err, "no github token found") + return fmt.Errorf("getting Github PAT from environment at variable %s: %v", github.PersonalAccessTokenEnvVar, err) + } + + ghUser := github.NewGitHubUser(user, email, token) + // Creating git client in memory and clone 'eks-distro-build-tooling + forkUrl := fmt.Sprintf(constants.EksGoRepoUrl, ghUser.User()) + gClient := git.NewClient(git.WithInMemoryFilesystem(), git.WithRepositoryUrl(forkUrl), git.WithAuth(&http.BasicAuth{Username: ghUser.User(), Password: ghUser.Token()})) + if err := gClient.Clone(ctx); err != nil { + logger.Error(err, "Cloning repo", "user", ghUser.User()) + return err + } + + // Increment Release + if err := bumpRelease(gClient, r); err != nil { + logger.Error(err, "increment release") + return err + } + + // Create new branch + if err := gClient.Branch(r.EksGoReleaseVersion()); err != nil { + logger.Error(err, "git branch", "branch name", r.EksGoReleaseVersion(), "repo", forkUrl, "client", gClient) + return err + } + + // Update files for new patch versions of golang + if err := updateVersionReadme(gClient, r); err != nil { + logger.Error(err, "Update Readme") + return err + } + + if err := updateGitTag(gClient, r); err != nil { + logger.Error(err, "Update GitTag") + return err + } + + /* ----- + * Begin applying previous patches and attempting to cherry-pick the new commit. Any errors from here on out should result in cutting a pr without a new patch, + * but shouldn't fail the automation because the patch can be generated manually + ----- */ + prSubject := fmt.Sprintf(backportPRSubjectFmt, r.EksGoReleaseVersion(), cve) + commitMsg := fmt.Sprintf(backportCommitMsgFmt, r.EksGoReleaseVersion()) + golangClient := git.NewClient(git.WithInMemoryFilesystem(), git.WithRepositoryUrl(constants.GoRepoUrl), git.WithAuth(&http.BasicAuth{Username: user, Password: token})) + if err := createPatchFile(ctx, r, gClient, golangClient, commit); err != nil { + logger.V(3).Info("Generate Patch failed, continuing with PR") + // no longer update gospec with patch file since no patch was created + if !dryrun { + prFailureDescription := fmt.Sprintf(backportPRDescriptionFailureFmt, cve, r.EksGoReleaseVersion(), commit) + if err := createReleasePR(ctx, dryrun, r, ghUser, gClient, prSubject, prFailureDescription, commitMsg); err != nil { + logger.Error(err, "Create Release PR") + return err + } + } + } + + if !dryrun { + prSuccessDescription := fmt.Sprintf(backportPRDescriptionSuccessFmt, cve, commit, r.EksGoReleaseVersion()) + if err := createReleasePR(ctx, dryrun, r, ghUser, gClient, prSubject, prSuccessDescription, commitMsg); err != nil { + logger.Error(err, "Create Release PR") + } + } + return nil +} diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/eksGoRelease.go b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/eksGoRelease.go new file mode 100644 index 000000000..6458b6877 --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/eksGoRelease.go @@ -0,0 +1,125 @@ +package eksGoRelease + +import ( + "fmt" + "strconv" + "strings" + + "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/constants" + "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/logger" +) + +func NewEksGoReleaseObject(versionString string) (*Release, error) { + splitVersion := strings.Split(versionString, ".") + major, err := strconv.Atoi(splitVersion[0]) + if err != nil { + return nil, fmt.Errorf("parsing major version: %v", err) + } + + minor, err := strconv.Atoi(splitVersion[1]) + if err != nil { + return nil, fmt.Errorf("parsing minor version: %v", err) + } + + patch, err := strconv.Atoi(splitVersion[2]) + if err != nil { + return nil, fmt.Errorf("parsing patch version: %v", err) + } + + return &Release{ + Major: major, + Minor: minor, + Patch: patch, + }, nil +} + +type Release struct { + Major int + Minor int + Patch int + Release int +} + +func (r Release) GoReleaseBranch() string { + return fmt.Sprintf("release-branch.go%d.%d", r.Major, r.Minor) +} + +func (r Release) MajorVersion() int { + return r.Major +} + +func (r Release) MinorVersion() int { + return r.Minor +} + +func (r Release) PatchVersion() int { + return r.Patch +} + +func (r Release) ReleaseNumber() int { + return r.Release +} + +func (r Release) EksGoReleaseVersion() string { + return fmt.Sprintf("v%d.%d.%d-%d", r.Major, r.Minor, r.Patch, r.Release) +} + +func (r Release) GoFullVersion() string { + return fmt.Sprintf("%d.%d.%d", r.Major, r.Minor, r.Patch) +} + +func (r Release) GoMinorVersion() string { + return fmt.Sprintf("%d.%d", r.Major, r.Minor) +} + +func (r Release) GoSemver() string { + return fmt.Sprintf("v%d.%d.%d", r.Major, r.Minor, r.Patch) +} + +// "https://distro.eks.amazonaws.com/golang-go%d.%d.%d/release/%d/%s/%s/%s" +func (r Release) EksGoArtifacts(arch string) (string, string, string) { + var artifact string // artifact = "golang-%d.%d.%d-%d.amzn2.eks.%s.rpm" + var urlFmt string // artifact = "golang-%d.%d.%d-%d.amzn2.eks.%s.rpm" + + switch arch { + case "x86_64", "aarch64": + artifact = fmt.Sprintf(constants.EksGoRpmArtifactFmt, r.Major, r.Minor, r.Patch, r.Release, arch) + urlFmt = fmt.Sprintf(constants.EksGoArtifactUrl, r.Major, r.Minor, r.Patch, r.Release, arch, "RPMS", arch) + case "noarch": + artifact = fmt.Sprintf(constants.EksGoNoarchRpmArtifactFmt, r.MajorVersion(), r.MinorVersion(), r.PatchVersion(), r.ReleaseNumber(), arch) + urlFmt = fmt.Sprintf(constants.EksGoArtifactUrl, r.Major, r.Minor, r.Patch, r.Release, "x86_64", "RPMS", arch) + case "amd64", "arm64": + artifact = fmt.Sprintf(constants.EksGoTargzArtifactFmt, r.Major, r.Minor, r.Patch, arch) + urlFmt = fmt.Sprintf(constants.EksGoArtifactUrl, r.Major, r.Minor, r.Patch, r.Release, "archives", "linux", arch) + } + + return artifact, fmt.Sprintf("%s.sha256", artifact), urlFmt +} + +func (r Release) EksGoAmdBuild() string { + return fmt.Sprintf(constants.EksGoAmdBuildUrl, r.Major, r.Minor) +} + +func (r Release) EksGoArmBuild() string { + return fmt.Sprintf(constants.EksGoArmBuildUrl, r.Major, r.Minor) +} + +func (r Release) Equals(release Release) bool { + if r.Major != release.MajorVersion() { + logger.V(4).Info("Major version not equal", "self Major", r.Major, "compare Major", release.MajorVersion()) + return false + } + if r.Minor != release.MinorVersion() { + logger.V(4).Info("Minor version not equal", "self Minor", r.Minor, "compare Minor", release.MinorVersion()) + return false + } + if r.Patch != release.PatchVersion() { + logger.V(4).Info("Patch version not equal", "self Patch", r.Patch, "compare Patch", release.PatchVersion()) + return false + } + if r.Release != release.ReleaseNumber() { + logger.V(4).Info("Release version not equal", "self Release", r.Release, "compare Release", release.ReleaseNumber()) + return false + } + return true +} diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/eksGoRelease_test.go b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/eksGoRelease_test.go new file mode 100644 index 000000000..d6a6b9661 --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/eksGoRelease_test.go @@ -0,0 +1,31 @@ +package eksGoRelease_test + +import ( + "testing" + + "github.com/aws/eks-distro-build-tooling/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease" +) + +func TestIssueManagerCreateIssueSuccess(t *testing.T) { + releaseObject, err := eksGoRelease.NewEksGoReleaseObject("1.25.5") + if err != nil { + t.Errorf("NewEksGoReleaseObject error = %v, want nil", err) + } + + testReleaseObject := newTestEksGoRelease(t) + + releasesAreEqual := releaseObject.Equals(testReleaseObject) + if !releasesAreEqual { + t.Errorf("EKS Go Release object is not equal to the test Release object! Release object: %v, testReleaseObject: %v", releaseObject, testReleaseObject) + } +} + +func newTestEksGoRelease(t *testing.T) eksGoRelease.Release { + //TODO: Update the release value from -1 once we validate needing it and move to a better test value or remove. + return eksGoRelease.Release{ + Major: 1, + Minor: 25, + Patch: 5, + Release: 0, + } +} diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/githubRelease.go b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/githubRelease.go new file mode 100644 index 000000000..7082fc40f --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/githubRelease.go @@ -0,0 +1,367 @@ +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 + * + * 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 { + logger.Error(err, "Reading file", "file", releasePath) + return err + } + // We need to check there isn't a \n character if there is we only take the first value + if len(content) > 1 { + content = content[0: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 { + 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(rpmSourcePathFmt, 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 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: ghUser.User(), + } + prm := prManager.New(retrier, githubClient, prmOpts) + + prOpts := &prManager.CreatePrOpts{ + CommitBranch: r.EksGoReleaseVersion(), + 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 +} diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/newVersion.go b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/newVersion.go new file mode 100644 index 000000000..ebacc6979 --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/newVersion.go @@ -0,0 +1,74 @@ +package eksGoRelease + +import ( + "context" + "fmt" + + "github.com/go-git/go-git/v5/plumbing/transport/http" + + "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" +) + +const ( + newMinorVersionCommitMsgFmt = "Init new Go Minor Version %s files." + newMinorVersionPRSubjectFmt = "New minor release of Golang: %s" + newMinorVersionPRDescriptionFmt = "Update EKS Go Patch Version: %s\nSPEC FILE STILL NEEDS THE '%%changelog' UPDATED\nPLEASE UPDATE WITH THE FOLLOWING FORMAT\n```\n* Wed Sep 06 2023 Cameron Rozean <rcrozean@amazon.com> - 1.20.8-1\n- Bump tracking patch version to 1.20.8 from 1.20.7\n```" +) + +// Releasing new versions of Golang that don't exist in EKS Distro Build Tooling(https://github.com/aws/eks-distro-build-tooling/projects/golang/go) +func NewMinorRelease(ctx context.Context, r *Release, dryrun bool, email, user string) error { + // Setup Git Clients + token, err := github.GetGithubToken() + if err != nil { + logger.V(4).Error(err, "no github token found") + return fmt.Errorf("getting Github PAT from environment at variable %s: %v", github.PersonalAccessTokenEnvVar, err) + } + + ghUser := github.NewGitHubUser(user, email, token) + // Creating git client in memory and clone 'eks-distro-build-tooling + forkUrl := fmt.Sprintf(constants.EksGoRepoUrl, ghUser.User()) + gClient := git.NewClient(git.WithInMemoryFilesystem(), git.WithRepositoryUrl(forkUrl), git.WithAuth(&http.BasicAuth{Username: ghUser.User(), Password: ghUser.Token()})) + if err := gClient.Clone(ctx); err != nil { + logger.Error(err, "Cloning repo", "user", ghUser.User()) + return err + } + + // Create new branch + if err := gClient.Branch(r.EksGoReleaseVersion()); err != nil { + logger.Error(err, "git branch", "branch name", r.EksGoReleaseVersion(), "repo", forkUrl, "client", gClient) + return err + } + + if err := updateProjectReadme(gClient, r); err != nil { + logger.Error(err, "update project Readme") + return err + } + + // Create files for new minor versions of golang + if err := updateVersionReadme(gClient, r); err != nil { + logger.Error(err, "update version Readme") + return err + } + + if err := updateGitTag(gClient, r); err != nil { + logger.Error(err, "update Readme") + return err + } + + if err := addTempFilesForNewMinorVersion(gClient, r); err != nil { + logger.Error(err, "creating temporary files for new minor verions") + return err + } + + // Commit file and create PR if not dryrun + prSubject := fmt.Sprintf(newMinorVersionPRSubjectFmt, r.GoSemver()) + prDescription := fmt.Sprintf(newMinorVersionPRDescriptionFmt, r.EksGoReleaseVersion()) + commitMsg := fmt.Sprintf(newMinorVersionCommitMsgFmt, r.GoSemver()) + if err := createReleasePR(ctx, dryrun, r, ghUser, gClient, prSubject, prDescription, commitMsg); err != nil { + logger.Error(err, "Create Release PR") + } + return nil +} diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/release.go b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/release.go new file mode 100644 index 000000000..a7a34a4e2 --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/release.go @@ -0,0 +1,65 @@ +package eksGoRelease + +import ( + "context" + "fmt" + + "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/go-git/go-git/v5/plumbing/transport/http" +) + +const ( + releasePRCommitFmt = "Release EKS Go version %s" + releasePRDescriptionFmt = "Increment release file to publish new EKS Go artifacts for %s" + releasePRSubjectFmt = "Release EKS Go: %s" +) + +// UpdatePatchVersion is for updating the files in https://github.com/aws/eks-distro-build-tooling/golang/go for golang versions still maintained by upstream. +// For EKS Go versions that aren't maintained by upstream, the function is +func ReleaseArtifacts(ctx context.Context, r *Release, dryrun bool, email, user string) error { + // Setup Git Clients + token, err := github.GetGithubToken() + if err != nil { + logger.V(4).Error(err, "no github token found") + return fmt.Errorf("getting Github PAT from environment at variable %s: %v", github.PersonalAccessTokenEnvVar, err) + } + + ghUser := github.NewGitHubUser(user, email, token) + // Creating git client in memory and clone 'eks-distro-build-tooling + forkUrl := fmt.Sprintf(constants.EksGoRepoUrl, ghUser.User()) + gClient := git.NewClient(git.WithInMemoryFilesystem(), git.WithRepositoryUrl(forkUrl), git.WithAuth(&http.BasicAuth{Username: ghUser.User(), Password: ghUser.Token()})) + if err := gClient.Clone(ctx); err != nil { + logger.Error(err, "Cloning repo", "user", ghUser.User()) + return err + } + + // Increment Release + if err := bumpRelease(gClient, r); err != nil { + logger.Error(err, "increment release") + return err + } + + // Create new branch + if err := gClient.Branch(fmt.Sprintf("release-%s", r.GoMinorVersion())); err != nil { + logger.Error(err, "git branch", "branch name", r.GoMinorVersion(), "repo", forkUrl, "client", gClient) + return err + } + + // Increment release files + if err := updateRelease(gClient, r); err != nil { + logger.Error(err, "updating release file", "release", r.EksGoReleaseVersion()) + return err + } + + // Commit files and create PR + prSubject := fmt.Sprintf(releasePRSubjectFmt, r.EksGoReleaseVersion()) + prDescription := fmt.Sprintf(releasePRDescriptionFmt, r.EksGoReleaseVersion()) + commitMsg := fmt.Sprintf(releasePRCommitFmt, r.EksGoReleaseVersion()) + if err := createReleasePR(ctx, dryrun, r, ghUser, gClient, prSubject, prDescription, commitMsg); err != nil { + logger.Error(err, "Create Release PR") + } + return nil +} diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/updateRelease.go b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/updateRelease.go new file mode 100644 index 000000000..3bc3bc623 --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/updateRelease.go @@ -0,0 +1,77 @@ +package eksGoRelease + +import ( + "context" + "fmt" + + "github.com/go-git/go-git/v5/plumbing/transport/http" + + "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" +) + +const ( + updatePRCommitFmt = "Update EKS Go files for version %s" + updatePRDescriptionFmt = "Update EKS Go Patch Version: %s\nSPEC FILE STILL NEEDS THE '%%changelog' UPDATED\nPLEASE UPDATE WITH THE FOLLOWING FORMAT\n```\n* Wed Sep 06 2023 Cameron Rozean <rcrozean@amazon.com> - 1.20.8-1\n- Bump tracking patch version to 1.20.8 from 1.20.7\n```" + updatePRSubjectFmt = "New patch release of Golang: %s" +) + +// UpdatePatchVersion is for updating the files in https://github.com/aws/eks-distro-build-tooling/golang/go for golang versions still maintained by upstream. +// For EKS Go versions that aren't maintained by upstream, the function is +func UpdateVersion(ctx context.Context, r *Release, dryrun bool, email, user string) error { + // Setup Git Clients + token, err := github.GetGithubToken() + if err != nil { + logger.V(4).Error(err, "no github token found") + return fmt.Errorf("getting Github PAT from environment at variable %s: %v", github.PersonalAccessTokenEnvVar, err) + } + + ghUser := github.NewGitHubUser(user, email, token) + // Creating git client in memory and clone 'eks-distro-build-tooling + forkUrl := fmt.Sprintf(constants.EksGoRepoUrl, ghUser.User()) + gClient := git.NewClient(git.WithInMemoryFilesystem(), git.WithRepositoryUrl(forkUrl), git.WithAuth(&http.BasicAuth{Username: ghUser.User(), Password: ghUser.Token()})) + if err := gClient.Clone(ctx); err != nil { + logger.Error(err, "Cloning repo", "user", ghUser.User()) + return err + } + + // Increment Release + if err := bumpRelease(gClient, r); err != nil { + logger.Error(err, "increment release") + return err + } + + // Create new branch + if err := gClient.Branch(r.EksGoReleaseVersion()); err != nil { + logger.Error(err, "git branch", "branch name", r.EksGoReleaseVersion(), "repo", forkUrl, "client", gClient) + return err + } + + // Update files for new patch versions of golang + if err := updateVersionReadme(gClient, r); err != nil { + logger.Error(err, "Update Readme") + return err + } + + // Since this doesn't require a backport set and pass false + if err := updateGoSpec(gClient, r); err != nil { + logger.Error(err, "Update Readme") + return err + } + + if err := updateGitTag(gClient, r); err != nil { + logger.Error(err, "Update GitTag") + return err + } + + // Commit files and create PR + prSubject := fmt.Sprintf(updatePRSubjectFmt, r.GoSemver()) + prDescription := fmt.Sprintf(updatePRDescriptionFmt, r.EksGoReleaseVersion()) + commitMsg := fmt.Sprintf(updatePRCommitFmt, r.GoSemver()) + if err := createReleasePR(ctx, dryrun, r, ghUser, gClient, prSubject, prDescription, commitMsg); err != nil { + logger.Error(err, "Create Release PR") + } + return nil +} diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/readmeFmt.txt b/tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/versionReadmeFmt.txt similarity index 100% rename from tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/readmeFmt.txt rename to tools/eksDistroBuildToolingOpsTools/pkg/eksGoRelease/versionReadmeFmt.txt diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/git/errors.go b/tools/eksDistroBuildToolingOpsTools/pkg/git/errors.go index 77ce45eb3..8a2a90b01 100644 --- a/tools/eksDistroBuildToolingOpsTools/pkg/git/errors.go +++ b/tools/eksDistroBuildToolingOpsTools/pkg/git/errors.go @@ -3,9 +3,9 @@ package git import "fmt" type RepositoryDoesNotExistError struct { + Err error repository string owner string - Err error } func (e *RepositoryDoesNotExistError) Error() string { diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/git/gitClient.go b/tools/eksDistroBuildToolingOpsTools/pkg/git/gitClient.go index 56a97cb9c..5952b1dc9 100644 --- a/tools/eksDistroBuildToolingOpsTools/pkg/git/gitClient.go +++ b/tools/eksDistroBuildToolingOpsTools/pkg/git/gitClient.go @@ -1,6 +1,8 @@ package git -import "context" +import ( + "context" +) type Client interface { Add(filename string) error @@ -9,7 +11,16 @@ type Client interface { Commit(message string, opts ...CommitOpt) error Push(ctx context.Context) error Pull(ctx context.Context, branch string) error + Status() error Init() error Branch(name string) error ValidateRemoteExists(ctx context.Context) error + // filename for all the functions should be the full path from the repo base + CreateFile(filename string, contents []byte) error + CopyFile(curFile, dstFile string) error + MoveFile(curFile, dstFile string) error + DeleteFile(filename string) error + ModifyFile(filename string, contents []byte) error + ReadFile(filename string) (string, error) + ReadFiles(foldername string) (map[string]string, error) } diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/git/gogitClient.go b/tools/eksDistroBuildToolingOpsTools/pkg/git/gogitClient.go index 365a3d248..40620592b 100644 --- a/tools/eksDistroBuildToolingOpsTools/pkg/git/gogitClient.go +++ b/tools/eksDistroBuildToolingOpsTools/pkg/git/gogitClient.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - + "io" "os" "strings" "time" @@ -23,7 +23,7 @@ import ( ) const ( - gitTimeout = 60 * time.Second + gitTimeout = 600 * time.Second maxRetries = 5 backOffPeriod = 5 * time.Second emptyRepoError = "remote repository is empty" @@ -32,10 +32,10 @@ const ( type GogitClient struct { Auth transport.AuthMethod Client GoGit - RepoUrl string + Retrier *retrier.Retrier RepoDirectory *string + RepoUrl string InMemory bool - Retrier *retrier.Retrier } type Opt func(*GogitClient) @@ -96,37 +96,37 @@ func (g *GogitClient) Clone(ctx context.Context) error { } func (g *GogitClient) Add(filename string) error { - logger.V(3).Info("Opening directory", "directory", g.RepoDirectory) + logger.V(4).Info("Opening directory", "directory", g.RepoDirectory) r, err := g.Client.OpenRepo() if err != nil { return err } - logger.V(3).Info("Opening working tree") + logger.V(4).Info("Opening working tree") w, err := g.Client.OpenWorktree(r) if err != nil { return err } - logger.V(3).Info("Tracking specified files", "file", filename) + logger.V(4).Info("Tracking specified files", "file", filename) err = g.Client.AddGlob(filename, w) return err } func (g *GogitClient) Remove(filename string) error { - logger.V(3).Info("Opening directory", "directory", g.RepoDirectory) + logger.V(4).Info("Opening directory", "directory", g.RepoDirectory) r, err := g.Client.OpenRepo() if err != nil { return err } - logger.V(3).Info("Opening working tree") + logger.V(4).Info("Opening working tree") w, err := g.Client.OpenWorktree(r) if err != nil { return err } - logger.V(3).Info("Removing specified files", "file", filename) + logger.V(4).Info("Removing specified files", "file", filename) _, err = g.Client.Remove(filename, w) return err } @@ -134,32 +134,32 @@ func (g *GogitClient) Remove(filename string) error { type CommitOpt func(signature *object.Signature) func WithUser(user string) CommitOpt { - return func (o *object.Signature) { + return func(o *object.Signature) { o.Name = user } } func WithEmail(email string) CommitOpt { - return func (o *object.Signature) { + return func(o *object.Signature) { o.Email = email } } func (g *GogitClient) Commit(message string, opts ...CommitOpt) error { - logger.V(3).Info("Opening directory", "directory", g.RepoDirectory) + logger.V(4).Info("Opening directory", "directory", g.RepoDirectory) r, err := g.Client.OpenRepo() if err != nil { logger.Info("Failed while attempting to open repo") return err } - logger.V(3).Info("Opening working tree") + logger.V(4).Info("Opening working tree") w, err := g.Client.OpenWorktree(r) if err != nil { return err } - logger.V(3).Info("Generating Commit object...") + logger.V(4).Info("Generating Commit object...") commitSignature := &object.Signature{ When: time.Now(), } @@ -172,7 +172,7 @@ func (g *GogitClient) Commit(message string, opts ...CommitOpt) error { return err } - logger.V(3).Info("Committing Object to local repo", "repo", g.RepoDirectory) + logger.V(4).Info("Committing Object to local repo", "repo", g.RepoDirectory) finalizedCommit, err := g.Client.CommitObject(r, commit) if err != nil { return err @@ -183,7 +183,7 @@ func (g *GogitClient) Commit(message string, opts ...CommitOpt) error { } func (g *GogitClient) Push(ctx context.Context) error { - logger.V(3).Info("Pushing to remote", "repo", g.RepoDirectory) + logger.V(4).Info("Pushing to remote", "repo", g.RepoDirectory) r, err := g.Client.OpenRepo() if err != nil { return fmt.Errorf("err pushing: %v", err) @@ -197,7 +197,7 @@ func (g *GogitClient) Push(ctx context.Context) error { } func (g *GogitClient) Pull(ctx context.Context, branch string) error { - logger.V(3).Info("Pulling from remote", "repo", g.RepoDirectory, "remote", gogit.DefaultRemoteName) + logger.V(4).Info("Pulling from remote", "repo", g.RepoDirectory, "remote", gogit.DefaultRemoteName) r, err := g.Client.OpenRepo() if err != nil { return fmt.Errorf("pulling from remote: %v", err) @@ -213,7 +213,7 @@ func (g *GogitClient) Pull(ctx context.Context, branch string) error { err = g.Client.PullWithContext(ctx, w, g.Auth, branchRef) if errors.Is(err, gogit.NoErrAlreadyUpToDate) { - logger.V(3).Info("Local repo already up-to-date", "repo", g.RepoDirectory, "remote", gogit.DefaultRemoteName) + logger.V(4).Info("Local repo already up-to-date", "repo", g.RepoDirectory, "remote", gogit.DefaultRemoteName) return &RepositoryUpToDateError{} } @@ -230,7 +230,7 @@ func (g *GogitClient) Pull(ctx context.Context, branch string) error { if err != nil { return fmt.Errorf("accessing latest commit after pulling from remote: %v", err) } - logger.V(3).Info("Successfully pulled from remote", "repo", g.RepoDirectory, "remote", gogit.DefaultRemoteName, "latest commit", commit.Hash) + logger.V(4).Info("Successfully pulled from remote", "repo", g.RepoDirectory, "remote", gogit.DefaultRemoteName, "latest commit", commit.Hash) return nil } @@ -269,11 +269,11 @@ func (g *GogitClient) Branch(name string) error { } if branchExistsLocally { - logger.V(3).Info("Branch already exists locally", "branch", name) + logger.V(4).Info("Branch already exists locally", "branch", name) } if !branchExistsLocally { - logger.V(3).Info("Branch does not exist locally", "branch", name) + logger.V(4).Info("Branch does not exist locally", "branch", name) headref, err := g.Client.Head(r) if err != nil { return fmt.Errorf("creating branch %s: %v", name, err) @@ -307,7 +307,7 @@ func (g *GogitClient) Branch(name string) error { } func (g *GogitClient) ValidateRemoteExists(ctx context.Context) error { - logger.V(3).Info("Validating git setup", "repoUrl", g.RepoUrl) + logger.V(4).Info("Validating git setup", "repoUrl", g.RepoUrl) remote := g.Client.NewRemote(g.RepoUrl, gogit.DefaultRemoteName) // Check if we are able to make a connection to the remote by attempting to list refs _, err := g.Client.ListWithContext(ctx, remote, g.Auth) @@ -317,6 +317,28 @@ func (g *GogitClient) ValidateRemoteExists(ctx context.Context) error { return nil } +func (g *GogitClient) Status() error { + repo, err := g.Client.OpenRepo() + if err != nil { + logger.Error(err, "Opening repo") + return err + } + + wt, err := repo.Worktree() + if err != nil { + logger.Error(err, "Accessing worktree") + return err + } + + s, err := wt.Status() + if err != nil { + logger.Error(err, "git status") + return err + } + logger.V(4).Info("git status", "status", s) + return nil +} + func (g *GogitClient) pullIfRemoteExists(r *gogit.Repository, w *gogit.Worktree, branchName string, localBranchRef plumbing.ReferenceName) error { err := g.Retrier.Retry(func() error { remoteExists, err := g.remoteBranchExists(r, localBranchRef) @@ -352,6 +374,202 @@ func (g *GogitClient) remoteBranchExists(r *gogit.Repository, localBranchRef plu return false, nil } +func (g *GogitClient) CreateFile(filename string, contents []byte) error { + repo, err := g.Client.OpenRepo() + if err != nil { + logger.Error(err, "Opening repo") + return err + } + + wt, err := repo.Worktree() + if err != nil { + logger.Error(err, "Accessing worktree") + return err + } + + file, err := wt.Filesystem.Create(filename) + if err != nil { + logger.Error(err, "file creation", filename) + } + + _, err = file.Write(contents) + if err != nil { + logger.Error(err, "writing to file", filename, "contents", contents) + return err + } + + if err := file.Close(); err != nil { + return err + } + logger.V(4).Info("New file created", "file", filename) + return nil +} + +func (g *GogitClient) MoveFile(curPath, dstPath string) error { + repo, err := g.Client.OpenRepo() + if err != nil { + logger.Error(err, "Opening repo") + return err + } + + wt, err := repo.Worktree() + if err != nil { + logger.Error(err, "Accessing worktree") + return err + } + + if err := wt.Filesystem.Rename(curPath, dstPath); err != nil { + logger.Error(err, "moving file", "current", curPath, "destination", dstPath) + return err + } + + logger.V(4).Info("File moved", "file", dstPath) + return nil +} + +func (g *GogitClient) CopyFile(curPath, dstPath string) error { + repo, err := g.Client.OpenRepo() + if err != nil { + logger.Error(err, "Opening repo") + return err + } + + wt, err := repo.Worktree() + if err != nil { + logger.Error(err, "Accessing worktree") + return err + } + + curFile, err := wt.Filesystem.Open(curPath) + if err != nil { + logger.Error(err, "Opening file", curPath) + return err + } + + dstFile, err := wt.Filesystem.Create(dstPath) + if err != nil { + logger.Error(err, "creating file", dstFile) + return err + } + + if _, err = io.Copy(curFile, dstFile); err != nil { + logger.Error(err, "copying file", "original", curFile, "destination", dstFile) + return err + } + + if err := curFile.Close(); err != nil { + return err + } + if err := dstFile.Close(); err != nil { + return err + } + + logger.V(4).Info("File copied", "destination file", dstFile) + return nil +} + +func (g *GogitClient) DeleteFile(filename string) error { + repo, err := g.Client.OpenRepo() + if err != nil { + logger.Error(err, "Opening repo") + return err + } + + wt, err := repo.Worktree() + if err != nil { + logger.Error(err, "Accessing worktree") + return err + } + + file, err := wt.Filesystem.Create(filename) + if err != nil { + logger.Error(err, "file creation", filename) + } + + if err := file.Close(); err != nil { + return err + } + logger.V(4).Info("New file created", "file", filename) + return nil +} + +func (g *GogitClient) ModifyFile(filename string, contents []byte) error { + return g.CreateFile(filename, contents) +} + +func (g *GogitClient) ReadFile(filename string) (string, error) { + repo, err := g.Client.OpenRepo() + if err != nil { + logger.Error(err, "Opening repo") + return "", err + } + + ref, err := g.Client.Head(repo) + if err != nil { + logger.Error(err, "repo ref") + return "", err + } + + commit, err := repo.CommitObject(ref.Hash()) + if err != nil { + logger.Error(err, "commit") + return "", err + } + + tree, err := repo.TreeObject(commit.TreeHash) + if err != nil { + logger.Error(err, "tree") + return "", err + } + + file, err := tree.File(filename) + if err != nil { + logger.Error(err, "finding filename", "filename", filename) + return "", err + } + + return file.Contents() +} + +func (g *GogitClient) ReadFiles(foldername string) (map[string]string, error) { + repo, err := g.Client.OpenRepo() + if err != nil { + logger.Error(err, "Opening repo") + return nil, err + } + + ref, err := g.Client.Head(repo) + if err != nil { + return nil, err + } + commit, err := repo.CommitObject(ref.Hash()) + if err != nil { + return nil, err + } + + tree, err := repo.TreeObject(commit.TreeHash) + if err != nil { + return nil, err + } + + files := make(map[string]string) + err = tree.Files().ForEach(func(f *object.File) error { + if strings.Contains(f.Name, foldername) { + p, err := f.Contents() + if err != nil { + return err + } + files[f.Name] = p + } + return nil + }) + if err != nil { + return nil, fmt.Errorf("reading files from folder: %s, %v", foldername, err) + } + + return files, nil +} + type GoGit interface { AddGlob(f string, w *gogit.Worktree) error Checkout(w *gogit.Worktree, opts *gogit.CheckoutOptions) error @@ -375,8 +593,8 @@ type GoGit interface { WithRepositoryDirectory(dir string) } -type goGit struct{ - storer *memory.Storage +type goGit struct { + storer *memory.Storage worktreeFilesystem billy.Filesystem repositoryDirectory string } @@ -393,12 +611,13 @@ func (gg *goGit) CloneInMemory(ctx context.Context, repourl string, auth transpo gg.storer = memory.NewStorage() } - return gogit.CloneContext(ctx, gg.storer, nil, &gogit.CloneOptions{ + return gogit.CloneContext(ctx, gg.storer, gg.worktreeFilesystem, &gogit.CloneOptions{ Auth: auth, URL: repourl, Progress: os.Stdout, }) } + func (gg *goGit) Clone(ctx context.Context, dir string, repourl string, auth transport.AuthMethod) (*gogit.Repository, error) { ctx, cancel := context.WithTimeout(ctx, gitTimeout) defer cancel() @@ -515,4 +734,4 @@ func (gg *goGit) ListWithContext(ctx context.Context, r *gogit.Remote, auth tran func (gg *goGit) SetRepositoryReference(r *gogit.Repository, p *plumbing.Reference) error { return r.Storer.SetReference(p) -} \ No newline at end of file +} diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/github/personalAccessToken.go b/tools/eksDistroBuildToolingOpsTools/pkg/github/personalAccessToken.go index 828f682c7..ec73acc9f 100644 --- a/tools/eksDistroBuildToolingOpsTools/pkg/github/personalAccessToken.go +++ b/tools/eksDistroBuildToolingOpsTools/pkg/github/personalAccessToken.go @@ -9,12 +9,14 @@ const ( PersonalAccessTokenEnvVar = "GITHUB_TOKEN" ) -func GetGithubToken() (string, error){ - t, ok := os.LookupEnv(PersonalAccessTokenEnvVar); if !ok { - return "", fmt.Errorf("Github Token environment variable %s not set", PersonalAccessTokenEnvVar) +func GetGithubToken() (string, error) { + t, ok := os.LookupEnv(PersonalAccessTokenEnvVar) + if !ok { + return "", fmt.Errorf("github token environment variable %s not set", PersonalAccessTokenEnvVar) } if t == "" { - return "", fmt.Errorf("Github Token enviornment variable %s is empty", PersonalAccessTokenEnvVar) + return "", fmt.Errorf("github token enviornment variable %s is empty", PersonalAccessTokenEnvVar) } return t, nil -} \ No newline at end of file +} + diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/github/user.go b/tools/eksDistroBuildToolingOpsTools/pkg/github/user.go new file mode 100644 index 000000000..218f54059 --- /dev/null +++ b/tools/eksDistroBuildToolingOpsTools/pkg/github/user.go @@ -0,0 +1,27 @@ +package github + +type GitHubUser struct { + user string + email string + token string +} + +func NewGitHubUser(user, email, token string) GitHubUser { + return GitHubUser{ + user: user, + email: email, + token: token, + } +} + +func (ghu GitHubUser) User() string { + return ghu.user +} + +func (ghu GitHubUser) Email() string { + return ghu.email +} + +func (ghu GitHubUser) Token() string { + return ghu.token +} diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/prManager/prManager.go b/tools/eksDistroBuildToolingOpsTools/pkg/prManager/prManager.go index 085fa7229..c12dc23e6 100644 --- a/tools/eksDistroBuildToolingOpsTools/pkg/prManager/prManager.go +++ b/tools/eksDistroBuildToolingOpsTools/pkg/prManager/prManager.go @@ -1,4 +1,4 @@ -package prmanager +package prManager import ( "context" @@ -209,21 +209,27 @@ type CreatePrOpts struct { } func (p *PrCreator) CreatePr(ctx context.Context, opts *CreatePrOpts) (string, error) { - ref, err := p.getRef(ctx, opts.CommitBranch, opts.BaseBranch) - if err != nil { - return "", fmt.Errorf("creating pull request: get/create the commit reference: %s\n", err) - } - if ref == nil { - return "", fmt.Errorf("creating pull request: the reference is nil") - } + // If opts.CommitMessage is not empty then create the required refs and other objects to create the commit + // otherwise this will try and create a pr from the repos referenced. This requires the branch to exist. + // Checkout the goGithub repo's Create function + // https://github.com/google/go-github/blob/a0e8f35c5cefc688733d566ec3701e86972df056/github/pulls.go#L258 + if opts.CommitMessage != "" { + ref, err := p.getRef(ctx, opts.CommitBranch, opts.BaseBranch) + if err != nil { + return "", fmt.Errorf("creating pull request: get/create the commit reference: %s\n", err) + } + if ref == nil { + return "", fmt.Errorf("creating pull request: the reference is nil") + } - tree, err := p.getTree(ctx, ref, opts.SourceFileBody, opts.DestFileGitPath) - if err != nil { - return "", fmt.Errorf("creating the tree based on the provided files: %s\n", err) - } + tree, err := p.getTree(ctx, ref, opts.SourceFileBody, opts.DestFileGitPath) + if err != nil { + return "", fmt.Errorf("creating the tree based on the provided files: %s\n", err) + } - if err := p.pushCommit(ctx, ref, tree, opts.AuthorName, opts.AuthorEmail, opts.CommitMessage); err != nil { - return "", fmt.Errorf("creating the commit: %s\n", err) + if err := p.pushCommit(ctx, ref, tree, opts.AuthorName, opts.AuthorEmail, opts.CommitMessage); err != nil { + return "", fmt.Errorf("creating the commit: %s\n", err) + } } pr, err := p.createPR(ctx, opts) diff --git a/tools/eksDistroBuildToolingOpsTools/pkg/prManager/prManager_test.go b/tools/eksDistroBuildToolingOpsTools/pkg/prManager/prManager_test.go index 4914a504a..5dfde3515 100644 --- a/tools/eksDistroBuildToolingOpsTools/pkg/prManager/prManager_test.go +++ b/tools/eksDistroBuildToolingOpsTools/pkg/prManager/prManager_test.go @@ -1,4 +1,4 @@ -package prmanager_test +package prManager_test import ( "context" @@ -39,7 +39,7 @@ var ( testDestFileGitPath = "test.txt" testBaseBranch = "main" testCommitBranch = "testCommitBranch" - ) +) func TestPrManagerCreatePRSuccess(t *testing.T) { ctx := context.Background() @@ -59,8 +59,8 @@ func TestPrManagerCreatePRSuccess(t *testing.T) { } initialGitTree := &gogithub.Tree{ - SHA: &treeRootSha, - Entries: nil, + SHA: &treeRootSha, + Entries: nil, Truncated: gogithub.Bool(false), } @@ -98,7 +98,6 @@ func TestPrManagerCreatePRSuccess(t *testing.T) { } } - func TestPrManagerCreatePRSuccessAlternatePath(t *testing.T) { ctx := context.Background() pr := newTestPrManager(t) @@ -117,8 +116,8 @@ func TestPrManagerCreatePRSuccessAlternatePath(t *testing.T) { } initialGitTree := &gogithub.Tree{ - SHA: &treeRootSha, - Entries: nil, + SHA: &treeRootSha, + Entries: nil, Truncated: gogithub.Bool(false), } @@ -191,9 +190,9 @@ func givenRetrier() *retrier.Retrier { } type testPrManager struct { - prManager *prmanager.PrCreator - prClient *githubMocks.MockPullRequestClient - gitClient *githubMocks.MockGitClient + prManager *prmanager.PrCreator + prClient *githubMocks.MockPullRequestClient + gitClient *githubMocks.MockGitClient repoClient *githubMocks.MockRepoClient } @@ -204,7 +203,7 @@ func newTestPrManager(t *testing.T) testPrManager { repoClient := githubMocks.NewMockRepoClient(mockCtrl) githubClient := &github.Client{ PullRequests: prClient, - Git: gitClient, + Git: gitClient, Repositories: repoClient, } @@ -216,18 +215,18 @@ func newTestPrManager(t *testing.T) testPrManager { } return testPrManager{ - prClient: prClient, - gitClient: gitClient, + prClient: prClient, + gitClient: gitClient, repoClient: repoClient, - prManager: prmanager.New(givenRetrier(), githubClient, o), + prManager: prmanager.New(givenRetrier(), githubClient, o), } } func getCommitExpectedCommit() *gogithub.RepositoryCommit { commit := &gogithub.Commit{ - Author: commitAuthor(), + Author: commitAuthor(), Message: &testCommitMessage, - Tree: nil, + Tree: nil, Parents: []*gogithub.Commit{ { SHA: &baseBranchSha, @@ -236,26 +235,26 @@ func getCommitExpectedCommit() *gogithub.RepositoryCommit { } return &gogithub.RepositoryCommit{ - SHA: gogithub.String(parentCommitSha), - Commit: commit, + SHA: gogithub.String(parentCommitSha), + Commit: commit, } } func commitAuthor() *gogithub.CommitAuthor { date := time.Now() return &gogithub.CommitAuthor{ - Date: &date, - Name: &testCommitAuthor, + Date: &date, + Name: &testCommitAuthor, Email: &testCommitAuthorEmail, } } func newCommit() *gogithub.Commit { return &gogithub.Commit{ - Author: commitAuthor(), + Author: commitAuthor(), Message: &testCommitMessage, - Tree: nil, - SHA: gogithub.String(testNewCommitSha), + Tree: nil, + SHA: gogithub.String(testNewCommitSha), Parents: []*gogithub.Commit{ { SHA: &baseBranchSha,