Skip to content

Commit

Permalink
Add rollouts auto-upgrade script
Browse files Browse the repository at this point in the history
Signed-off-by: Jonathan West <[email protected]>
  • Loading branch information
jgwest committed Jan 30, 2024
1 parent 5eda133 commit f95ebf8
Show file tree
Hide file tree
Showing 4 changed files with 356 additions and 0 deletions.
23 changes: 23 additions & 0 deletions hack/upgrade-rollouts-script/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Update argo-rollouts-manager to latest release of Argo Rollouts

The Go code and script this in this directory will automatically open a pull request to update the argo-rollouts-manager to the latest official argo-rollouts release:
- Update container image version in 'default.go'
- Update go.mod to point to latest module version
- Update CRDs to latest
- Open Pull Request using 'gh' CLI

## Instructions

### Pre-requisites:
- GitHub CLI (_gh_) installed and on PATH
- Operator-sdk v1.28.0 installed (as of January 2024), and on PATH
- Go installed an on PATH

### To run the tool

Modify the `init-repo.sh` file, updating the GitHub URL with a fork.

Then run the script:
```bash
./go-run.sh
```
8 changes: 8 additions & 0 deletions hack/upgrade-rollouts-script/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/jgwest/argo-rollouts-release-job

go 1.20

require (
github.com/google/go-github/v58 v58.0.0
github.com/google/go-querystring v1.1.0 // indirect
)
7 changes: 7 additions & 0 deletions hack/upgrade-rollouts-script/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-github/v58 v58.0.0 h1:Una7GGERlF/37XfkPwpzYJe0Vp4dt2k1kCjlxwjIvzw=
github.com/google/go-github/v58 v58.0.0/go.mod h1:k4hxDKEfoWpSqFlc8LTpGd9fu2KrV1YAa6Hi6FmDNY4=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
318 changes: 318 additions & 0 deletions hack/upgrade-rollouts-script/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
package main

import (
"bytes"
"context"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"sort"
"strings"

"os/exec"

"github.com/google/go-github/v58/github"
)

const (
PRTitle = "Upgrade to Argo Rollouts "
argoRolloutsRepoOrg = "argoproj"
argoRolloutsRepoName = "argo-rollouts"

argoprojlabsRepoOrg = "argoproj-labs"
argoRolloutsManagerRepoName = "argo-rollouts-manager"
)

func main() {

pathToGitHubRepo := "argo-rollouts-manager"

gitHubToken := os.Getenv("GH_TOKEN")
if gitHubToken == "" {
exitWithError(fmt.Errorf("missing GH_TOKEN"))
return
}

client := github.NewClient(nil).WithAuthToken(gitHubToken)

// 1) Check for existing version update PRs on the repo

// prList, _, err := client.PullRequests.List(context.Background(), argoprojlabsRepoOrg, argoRolloutsManagerRepoName, &github.PullRequestListOptions{})
// if err != nil {
// exitWithError(err)
// return
// }
// for _, pr := range prList {
// if strings.HasPrefix(*pr.Title, PRTitle) {
// exitWithError(fmt.Errorf("PR already exists"))
// return
// }
// }

// 2) Pull the latest releases from rollouts repo

releases, _, err := client.Repositories.ListReleases(context.Background(), argoRolloutsRepoOrg, argoRolloutsRepoName, &github.ListOptions{})
if err != nil {
exitWithError(err)
return
}

var firstProperRelease *github.RepositoryRelease

for _, release := range releases {

if strings.Contains(*release.TagName, "rc") {
continue
}
firstProperRelease = release
break
}

if firstProperRelease == nil {
exitWithError(fmt.Errorf("no release found"))
return
}

newBranchName := "upgrade-to-rollouts-" + *firstProperRelease.TagName

// 3) Create, commit, and push a new branch
if err := createNewCommitAndBranch(*firstProperRelease.TagName, "quay.io/argoproj/argo-rollouts", newBranchName, pathToGitHubRepo); err != nil {
exitWithError(err)
return
}

// // 4) Create PR if it doesn't exist
// if stdout, stderr, err := runCommandWithWD(pathToGitHubRepo, "gh", "pr", "create",
// "-R", argoprojlabsRepoOrg+"/"+argoRolloutsManagerRepoName,
// "--title", PRTitle+(*firstProperRelease.TagName), "--body", "Update to latest release of Argo Rollouts"); err != nil {
// fmt.Println(stdout, stderr)
// exitWithError(err)
// return
// }

}

func createNewCommitAndBranch(latestReleaseVersionTag string, latestReleaseVersionImage, newBranchName, pathToGitRepo string) error {

commands := [][]string{
{"git", "fetch", "parent"},
{"git", "checkout", "main"},
{"git", "rebase", "parent/main"},
{"git", "checkout", "-b", newBranchName},
}

if err := runCommandListWithWD(pathToGitRepo, commands); err != nil {
return err
}

if err := regenerateControllersDefaultGo(latestReleaseVersionTag, latestReleaseVersionImage, pathToGitRepo); err != nil {
return err
}

if err := regenerateGoMod(latestReleaseVersionTag, pathToGitRepo); err != nil {
return err
}

rolloutsRepoPath, err := checkoutRolloutsRepo(latestReleaseVersionTag)
if err != nil {
return err
}

crdPath := filepath.Join(rolloutsRepoPath, "manifests/crds")
crdYamlDirEntries, err := os.ReadDir(crdPath)
if err != nil {
return err
}

var crdYAMLs []string
for _, crdYamlDirEntry := range crdYamlDirEntries {

if crdYamlDirEntry.Name() == "kustomization.yaml" {
continue
}

if !crdYamlDirEntry.IsDir() {
crdYAMLs = append(crdYAMLs, crdYamlDirEntry.Name())
}
}

sort.Strings(crdYAMLs)

// NOTE: If this line fails, check if any new CRDs have been added to Rollouts, and/or if they have changed the filenames.
// - If so, this will require verifying the changes, then updating this list
if !reflect.DeepEqual(crdYAMLs, []string{
"analysis-run-crd.yaml",
"analysis-template-crd.yaml",
"cluster-analysis-template-crd.yaml",
"experiment-crd.yaml",
"rollout-crd.yaml"}) {
return fmt.Errorf("unexpected CRDs found: %v", crdYAMLs)
}

destinationPath := filepath.Join(pathToGitRepo, "config/crd/bases")
for _, crdYAML := range crdYAMLs {

destFile, err := os.Create(filepath.Join(destinationPath, crdYAML))
if err != nil {
return fmt.Errorf("unable to create file for '%s': %w", crdYAML, err)
}
defer destFile.Close()

srcFile, err := os.Open(filepath.Join(crdPath, crdYAML))
if err != nil {
return fmt.Errorf("unable to open source file for '%s': %w", crdYAML, err)
}
defer srcFile.Close()

_, err = io.Copy(destFile, srcFile)
if err != nil {
return fmt.Errorf("unable to copy file for '%s': %w", crdYAML, err)
}

}

commands = [][]string{
{"go", "mod", "tidy"},
{"make", "generate", "manifests"},
{"make", "bundle"},
{"git", "add", "--all"},
{"git", "commit", "-s", "-m", PRTitle + latestReleaseVersionTag},
{"git", "push", "-f", "--set-upstream", "origin", newBranchName},
}
if err := runCommandListWithWD(pathToGitRepo, commands); err != nil {
return err
}

return nil

}

func checkoutRolloutsRepo(latestReleaseVersionTag string) (string, error) {

tmpDir, err := os.MkdirTemp("", "argo-rollouts-src")
if err != nil {
return "", err
}

if _, _, err := runCommandWithWD(tmpDir, "git", "clone", "https://github.com/argoproj/argo-rollouts"); err != nil {
return "", err
}

newWD := filepath.Join(tmpDir, "argo-rollouts")

commands := [][]string{
{"git", "checkout", latestReleaseVersionTag},
}

if err := runCommandListWithWD(newWD, commands); err != nil {
return "", err
}

return newWD, nil
}

func runCommandListWithWD(workingDir string, commands [][]string) error {

for _, command := range commands {

_, _, err := runCommandWithWD(workingDir, command...)
if err != nil {
return err
}
}
return nil
}

func regenerateGoMod(latestReleaseVersionTag string, pathToGitRepo string) error {

// github.com/argoproj/argo-rollouts v1.6.3

path := filepath.Join(pathToGitRepo, "go.mod")

fileBytes, err := os.ReadFile(path)
if err != nil {
return err
}

var res string

for _, line := range strings.Split(string(fileBytes), "\n") {

if strings.Contains(line, "\tgithub.com/argoproj/argo-rollouts v") {

res += "\tgithub.com/argoproj/argo-rollouts " + latestReleaseVersionTag + "\n"

} else {
res += line + "\n"
}

}

if err := os.WriteFile(path, []byte(res), 0600); err != nil {
return err
}

return nil

}

func regenerateControllersDefaultGo(latestReleaseVersionTag string, latestReleaseVersionImage, pathToGitRepo string) error {

// DefaultArgoRolloutsVersion = "sha256:995450a0a7f7843d68e96d1a7f63422fa29b245c58f7b57dd0cf9cad72b8308f" //v1.4.1

path := filepath.Join(pathToGitRepo, "controllers/default.go")

fileBytes, err := os.ReadFile(path)
if err != nil {
return err
}

var res string

for _, line := range strings.Split(string(fileBytes), "\n") {

if strings.Contains(line, "DefaultArgoRolloutsVersion") {

res += " DefaultArgoRolloutsVersion = \"" + latestReleaseVersionTag + "\" // " + latestReleaseVersionTag + "\n"

} else {
res += line + "\n"
}

}

if err := os.WriteFile(path, []byte(res), 0600); err != nil {
return err
}

return nil

}

func runCommandWithWD(workingDir string, cmdList ...string) (string, string, error) {

fmt.Println(cmdList)

cmd := exec.Command(cmdList[0], cmdList[1:]...)
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Dir = workingDir
cmd.Stdout = &stdout
cmd.Stderr = &stderr

err := cmd.Run()
stdoutStr := stdout.String()
stderrStr := stderr.String()

fmt.Println(stdoutStr, stderrStr)

return stdoutStr, stderrStr, err

}

func exitWithError(err error) {
fmt.Println("ERROR:", err)
os.Exit(1)
}

0 comments on commit f95ebf8

Please sign in to comment.