diff --git a/cmd/rancher_release/README.md b/cmd/rancher_release/README.md index e5806522..ef6dd6ff 100644 --- a/cmd/rancher_release/README.md +++ b/cmd/rancher_release/README.md @@ -17,7 +17,7 @@ Results are printed in MD, and can be pasted into Slack, but formatting is trick **Examples** -``` +```sh rancher_release list-nonmirrored-rc-images --tag v2.8.0-rc1 ``` @@ -31,7 +31,7 @@ Checks if there’s an available Helm Chart and Docker images for amd64, arm and **Examples** -``` +```sh rancher_release check-rancher-image --tag v2.8.0-rc1 ``` @@ -56,24 +56,24 @@ Optional flags can be automatically set if you are inside your rancher fork. ⚠ **Examples** -``` +```sh rancher_release set-kdm-branch-refs -n dev-v2.8-september-patches --create-pr --dry-run ``` -``` +```sh export GITHUB_TOKEN={YOUR_GITHUB_TOKEN} rancher_release set-kdm-branch-refs -n dev-v2.8-september-patches -p -r ``` -``` +```sh rancher_release set-kdm-branch-refs --fork-path $GOPATH/src/github.com/{YOUR_USERNAME}/rancher \ --base-branch release/v2.8 \ --current-kdm-branch dev-v2.8 \ --new-kdm-branch dev-v2.8-september-patches ``` -``` +```sh export GITHUB_TOKEN={YOUR_GITHUB_TOKEN} rancher_release set-kdm-branch-refs -f $GOPATH/src/github.com/{YOUR_USERNAME}/rancher -b release/v2.8 -c dev-v2.8 -n dev-v2.8-september-patches -p -u {YOUR_USERNAME} @@ -100,19 +100,19 @@ Non-required flags can be automatically set, if you are inside your rancher fork **Examples** -``` +```sh export GITHUB_TOKEN={YOUR_GITHUB_TOKEN} rancher_release set-charts-branch-refs --new-charts-branch dev-v2.9 --create-pr --dry-run ``` -``` +```sh export GITHUB_TOKEN={YOUR_GITHUB_TOKEN} rancher_release set-charts-branch-refs -n dev-v2.9 -p -r ``` -``` +```sh rancher_release set-charts-branch-refs --fork-path $GOPATH/src/github.com/{YOUR_USERNAME}/rancher \ --base-branch release/v2.8 \ --current-charts-branch dev-v2.8 \ @@ -120,13 +120,51 @@ rancher_release set-charts-branch-refs --fork-path $GOPATH/src/github.com/{YOUR_ ``` -``` +```sh export GITHUB_TOKEN={YOUR_GITHUB_TOKEN} rancher_release set-charts-branch-refs -f $GOPATH/src/github.com/{YOUR_USERNAME}/rancher -b release/v2.8 -c dev-v2.8 -n dev-v2.9 -p -o {YOUR_USERNAME} ``` +### tag-release +Tags releases in GitHub for Rancher. + +When tagging a new release using the `tag-release` command, always prefer to use the default behavior of creating as a draft and verifying the release in the UI before publishing it. +If you are running this locally, you'll need to generate a GitHub Token, use the fine-grained personal access token, scoped to only the rancher repo and with the `contents read and write` scope. + + +| **Flag** | **Description** | **Required** | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `github-token`, `g`, `GITHUB_TOKEN` | GitHub generated token as described above. | TRUE | +| `tag`, `t` | The tag that you want to create. | TRUE | +| `remote-branch`, `b` | The branch which you want to create the tag against. | TRUE | +| `repo-owner`, `o` | Username of the rancher repo owner. Default is `rancher`, only customize this for testing purposes. | FALSE | +| `general-availability`, `a` | By default, the release will be created as a pre-release, before setting this as true, make sure it absolutely needs to be a GA release. | FALSE | +| `ignore-draft`, `d` | By default, the release will be created as a draft, so you can verify everything is correct before publishing it. | FALSE | +| `dry-run`, `r` | The release will not be created, just logged. | FALSE | + +**Examples** + +```sh +export GITHUB_TOKEN={YOUR_GITHUB_TOKEN} + +rancher_release tag-release --tag v2.8.0-rc1 --remote-branch release/v2.8 --dry-run +``` + +```sh +export GITHUB_TOKEN={YOUR_GITHUB_TOKEN} + +rancher_release tag-release --tag v2.8.0-rc1 --remote-branch release/v2.8 --repo-owner tashima42 --dry-run +``` + +```sh +export GITHUB_TOKEN={YOUR_GITHUB_TOKEN} + +rancher_release tag-release -t v2.8.0 -b release/v2.8 -a -r +``` + + ### label-issues Given a release candidate, updates each GitHub issue belonging to its milestone with the tag `[zube]: To Test` and adds a comment with the prerelease version to test. diff --git a/cmd/rancher_release/main.go b/cmd/rancher_release/main.go index d2bfd995..326b3667 100644 --- a/cmd/rancher_release/main.go +++ b/cmd/rancher_release/main.go @@ -33,6 +33,7 @@ func main() { setChartsBranchReferencesCommand(), checkRancherRCDepsCommand(), labelIssuesCommand(), + tagReleaseCommand(), } app.Flags = rootFlags diff --git a/cmd/rancher_release/tag_release.go b/cmd/rancher_release/tag_release.go new file mode 100644 index 00000000..0241d9b8 --- /dev/null +++ b/cmd/rancher_release/tag_release.go @@ -0,0 +1,75 @@ +package main + +import ( + "context" + + "github.com/rancher/ecm-distro-tools/release/rancher" + "github.com/rancher/ecm-distro-tools/repository" + "github.com/urfave/cli/v2" +) + +func tagReleaseCommand() *cli.Command { + return &cli.Command{ + Name: "tag-release", + Usage: "tag a rancher release", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "github-token", + Aliases: []string{"g"}, + EnvVars: []string{"GITHUB_TOKEN"}, + Required: true, + }, + &cli.StringFlag{ + Name: "tag", + Aliases: []string{"t"}, + Usage: "release tag", + Required: true, + }, + &cli.StringFlag{ + Name: "remote-branch", + Aliases: []string{"b"}, + Usage: "rancher remote branch", + Required: true, + }, + &cli.StringFlag{ + Name: "repo-owner", + Aliases: []string{"o"}, + Usage: "repo owner for the rancher repo, default is rancher, this is only used for testing purposes", + Value: "rancher", + Required: false, + }, + &cli.BoolFlag{ + Name: "general-availability", + Aliases: []string{"a"}, + Usage: "by default, the release will be created as a pre-release, make sure it absolutely needs to be a GA release before setting this", + Required: false, + }, + &cli.BoolFlag{ + Name: "ignore-draft", + Aliases: []string{"d"}, + Usage: "by default, the release will be created as a draft, so you can verify all information is correct before publishing it", + Required: false, + }, + &cli.BoolFlag{ + Name: "dry-run", + Aliases: []string{"r"}, + Usage: "skip all changes that have side effects, like creating a release in a remote repo.", + Required: false, + }, + }, + Action: tagRelease, + } +} + +func tagRelease(c *cli.Context) error { + token := c.String("github-token") + tag := c.String("tag") + remoteBranch := c.String("remote-branch") + repoOwner := c.String("repo-owner") + generalAvailability := c.Bool("general-availability") + ignoreDraft := c.Bool("ignore-draft") + dryRun := c.Bool("dry-run") + ctx := context.Background() + ghClient := repository.NewGithub(ctx, token) + return rancher.TagRancherRelease(ctx, ghClient, tag, remoteBranch, repoOwner, generalAvailability, ignoreDraft, dryRun) +} diff --git a/release/rancher/rancher.go b/release/rancher/rancher.go index 5660716d..8a578306 100644 --- a/release/rancher/rancher.go +++ b/release/rancher/rancher.go @@ -20,17 +20,20 @@ import ( ecmHTTP "github.com/rancher/ecm-distro-tools/http" "github.com/rancher/ecm-distro-tools/repository" "github.com/sirupsen/logrus" + "golang.org/x/mod/semver" "gopkg.in/yaml.v2" ) const ( + rancherOrg = "rancher" + rancherRepo = rancherOrg rancherImagesBaseURL = "https://github.com/rancher/rancher/releases/download/" rancherImagesFileName = "/rancher-images.txt" rancherHelmRepositoryURL = "https://releases.rancher.com/server-charts/latest/index.yaml" setKDMBranchReferencesScriptFileName = "set_kdm_branch_references.sh" setChartReferencesScriptFileName = `set_chart_references.sh` - cloneCheckoutRancherScript = `#!/bin/sh + navigateCheckoutRancherScript = `#!/bin/sh set -e BRANCH_NAME={{ .BranchName }} @@ -150,6 +153,20 @@ type HelmIndex struct { } `yaml:"entries"` } +type ContentLine struct { + Line int + File string + Content string +} + +type Content struct { + RancherImages []ContentLine + FilesWithRC []ContentLine + MinFilesWithRC []ContentLine + ChartsWithDev []ContentLine + KDMWithDev []ContentLine +} + func ListRancherImagesRC(tag string) (string, error) { downloadURL := rancherImagesBaseURL + tag + rancherImagesFileName imagesFile, err := rancherImages(downloadURL) @@ -257,7 +274,7 @@ func SetKDMBranchReferences(ctx context.Context, forkPath, rancherBaseBranch, ne BranchName: branchName, } - script := cloneCheckoutRancherScript + setKDMBranchReferencesScript + pushChangesScript + script := navigateCheckoutRancherScript + setKDMBranchReferencesScript + pushChangesScript logrus.Info("running update files and apply updates script...") output, err := exec.RunTemplatedScript(forkPath, setKDMBranchReferencesScriptFileName, script, data) if err != nil { @@ -291,7 +308,7 @@ func SetChartBranchReferences(ctx context.Context, forkPath, rancherBaseBranch, DryRun: dryRun, BranchName: branchName, } - script := cloneCheckoutRancherScript + setChartBranchReferencesScript + pushChangesScript + script := navigateCheckoutRancherScript + setChartBranchReferencesScript + pushChangesScript logrus.Info("running update files script") output, err := exec.RunTemplatedScript(forkPath, setChartReferencesScriptFileName, script, data) if err != nil { @@ -317,6 +334,46 @@ func SetChartBranchReferences(ctx context.Context, forkPath, rancherBaseBranch, return nil } +func TagRancherRelease(ctx context.Context, ghClient *github.Client, tag, remoteBranch, repoOwner string, generalAvailability, ignoreDraft, dryRun bool) error { + logrus.Info("validating tag semver format") + if !semver.IsValid(tag) { + return errors.New("the tag `" + tag + "` isn't a valid semantic versioning string") + } + logrus.Info("getting remote branch information from " + repoOwner + "/" + rancherRepo) + branch, _, err := ghClient.Repositories.GetBranch(ctx, repoOwner, rancherRepo, remoteBranch, true) + if err != nil { + return err + } + logrus.Info("the latest commit on branch " + remoteBranch + " is: " + *branch.Commit.SHA) + + createAsDraft := !ignoreDraft + createAsPrerelease := !generalAvailability + logrus.Info("creating release ") + ghRelease := github.RepositoryRelease{ + TagName: github.String(tag), + Name: github.String(rancherReleaseName(generalAvailability, tag)), + Draft: &createAsDraft, + Prerelease: &createAsPrerelease, + GenerateReleaseNotes: github.Bool(false), + } + logrus.Infof("github release: %+v", ghRelease) + if dryRun { + logrus.Info("dry run, skipping release creation") + return nil + } + _, _, err = ghClient.Repositories.CreateRelease(ctx, repoOwner, rancherRepo, &ghRelease) + return err +} + +func rancherReleaseName(generalAvailability bool, tag string) string { + releaseName := "" + if !generalAvailability { + releaseName += "Pre-release " + } + releaseName += tag + return releaseName +} + func createPRFromRancher(ctx context.Context, rancherBaseBranch, title, branchName, forkOwner string, ghClient *github.Client) error { pull := &github.NewPullRequest{ Title: github.String(title), @@ -329,20 +386,6 @@ func createPRFromRancher(ctx context.Context, rancherBaseBranch, title, branchNa return err } -type ContentLine struct { - Line int - File string - Content string -} - -type Content struct { - RancherImages []ContentLine - FilesWithRC []ContentLine - MinFilesWithRC []ContentLine - ChartsWithDev []ContentLine - KDMWithDev []ContentLine -} - func CheckRancherRCDeps(ctx context.Context, local, forCi bool, org, repo, commitHash, files string) error { var ( content Content diff --git a/release/rancher/rancher_test.go b/release/rancher/rancher_test.go index 8b1baffa..ad7e081c 100644 --- a/release/rancher/rancher_test.go +++ b/release/rancher/rancher_test.go @@ -1,6 +1,7 @@ package rancher import ( + "errors" "net/http" "net/http/httptest" "reflect" @@ -59,3 +60,18 @@ func TestRancherHelmChartVersions(t *testing.T) { t.Errorf("expected %v, got %v", expectedVersions, versions) } } + +func TestRancherReleaseName(t *testing.T) { + const tag = "v2.8.0" + const expectedGAReleaseName = tag + const expectedPreReleaseName = "Pre-release " + tag + + gaReleaseName := rancherReleaseName(true, tag) + if expectedGAReleaseName != gaReleaseName { + t.Error(errors.New("expected GA release name to be '" + expectedGAReleaseName + "' got '" + gaReleaseName + "' instead")) + } + preReleaseName := rancherReleaseName(false, tag) + if expectedPreReleaseName != preReleaseName { + t.Error(errors.New("expected GA release name to be '" + expectedPreReleaseName + "' got '" + preReleaseName + "' instead")) + } +}