diff --git a/hack/release-tools/chk_version.go b/hack/release-tools/chk_version.go new file mode 100644 index 000000000..394e6c379 --- /dev/null +++ b/hack/release-tools/chk_version.go @@ -0,0 +1,75 @@ +/* +Copyright 2020 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + "regexp" +) + +// This regex should match both our GA format (example: v1.4.3) and pre-release format (v1.2.4-beta.2) +// The following sub-capture groups are defined: +// major +// minor +// patch +// prerelease (this will be alpha/beta followed by a ".", followed by 1 or more digits (alpha.5) +var release_regex *regexp.Regexp = regexp.MustCompile("^v(?P[[:digit:]]+)\\.(?P[[:digit:]]+)\\.(?P[[:digit:]]+)(-{1}(?P(alpha|beta)\\.[[:digit:]]+))*") + +// This small program exists because checking the VELERO_VERSION rules in bash is difficult, and difficult to test for correctness. +// Calling it with --verify will verify whether or not the VELERO_VERSION environment variable is a valid version string, without parsing for its components. +// Calling it without --verify will try to parse the version into its component pieces. +func main() { + + velero_version := os.Getenv("VELERO_VERSION") + + submatches := reSubMatchMap(release_regex, velero_version) + + // Didn't match the regex, exit. + if len(submatches) == 0 { + fmt.Printf("VELERO_VERSION of %s was not valid. Please correct the value and retry.", velero_version) + os.Exit(1) + } + + if len(os.Args) > 1 && os.Args[1] == "--verify" { + os.Exit(0) + } + + // Send these in a bash variable format to stdout, so that they can be consumed by bash scripts that call the go program. + fmt.Printf("VELERO_MAJOR=%s\n", submatches["major"]) + fmt.Printf("VELERO_MINOR=%s\n", submatches["minor"]) + fmt.Printf("VELERO_PATCH=%s\n", submatches["patch"]) + fmt.Printf("VELERO_PRERELEASE=%s\n", submatches["prerelease"]) +} + +// reSubMatchMap returns a map with the named submatches within a regular expression populated as keys, and their matched values within a given string as values. +// If no matches are found, a nil map is returned +func reSubMatchMap(r *regexp.Regexp, s string) map[string]string { + match := r.FindStringSubmatch(s) + submatches := make(map[string]string) + if len(match) == 0 { + return submatches + } + for i, name := range r.SubexpNames() { + // 0 will always be empty from the return values of SubexpNames's documentation, so skip it. + if i != 0 { + submatches[name] = match[i] + } + } + + return submatches +} diff --git a/hack/release-tools/chk_version_test.go b/hack/release-tools/chk_version_test.go new file mode 100644 index 000000000..772e9beaa --- /dev/null +++ b/hack/release-tools/chk_version_test.go @@ -0,0 +1,71 @@ +/* +Copyright 2020 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "testing" +) + +func TestRegexMatching(t *testing.T) { + tests := []struct { + version string + expectMatch bool + }{ + { + version: "v1.4.0", + expectMatch: true, + }, + { + version: "v2.0.0", + expectMatch: true, + }, + { + version: "v1.5.0-alpha.1", + expectMatch: true, + }, + { + version: "v1.16.1320-beta.14", + expectMatch: true, + }, + { + version: "1.0.0", + expectMatch: false, + }, + { + // this is true because while the "--" is invalid, v1.0.0 is a valid part of the regex + version: "v1.0.0--beta.1", + expectMatch: true, + }, + } + + for _, test := range tests { + name := fmt.Sprintf("Testing version string %s", test.version) + t.Run(name, func(t *testing.T) { + results := reSubMatchMap(release_regex, test.version) + + if len(results) == 0 && test.expectMatch { + t.Fail() + } + + if len(results) > 0 && !test.expectMatch { + fmt.Printf("%v", results) + t.Fail() + } + }) + } +} diff --git a/hack/release-tools/tag-release.sh b/hack/release-tools/tag-release.sh new file mode 100755 index 000000000..f81a0b982 --- /dev/null +++ b/hack/release-tools/tag-release.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash + +# Copyright 2020 the Velero contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# This script will do the necessary checks and actions to create a release of Velero. +# It will first validate that all prerequisites are met, then verify the version string is what the user expects. +# A git tag will be created and pushed to GitHub, and GoReleaser will be invoked. + +# This script is meant to be a combination of documentation and executable. +# If you have questions at any point, please stop and ask! + +# Directory in which the script itself resides, so we can use it for calling programs that are in the same directory. +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +function tag_and_push() { + echo "Tagging and pushing $VELERO_VERSION" + git tag $VELERO_VERSION + git push $VELERO_VERSION +} + +# For now, have the person doing the release pass in the VELERO_VERSION variable as an environment variable. +# In the future, we might be able to inspect git via `git describe --abbrev=0` to get a hint for it. +if [[ -z "$VELERO_VERSION" ]]; then + printf "The \$VELERO_VERSION environment variable is not set. Please set it with\n\texport VELERO_VERSION=v\nthen try again." + exit 1 +fi + +# Make sure the user's provided their github token, so we can give it to goreleaser. +if [[ -z "$GITHUB_TOKEN" ]]; then + printf "The GITHUB_TOKEN environment variable is not set. Please set it with\n\t export GITHUB_TOKEN=\n then try again." + exit 1 +fi + + Ensure that we have a clean working tree before we let any changes happen, especially important for cutting release branches. +if [[ -n $(git status --short) ]]; then + echo "Your git working directory is dirty! Please clean up untracked files and stash any changes before proceeding." + exit 3 +fi + +# Make sure that there's no issue with the environment variable's format before trying to eval the parsed version. +if ! go run $DIR/chk_version.go --verify; then + exit 2 +fi +# Since we're past the validation of the VELERO_VERSION, parse the version's individual components. +eval $(go run $DIR/chk_version.go) + + +printf "To clarify, you've provided a version string of $VELERO_VERSION.\n" +printf "Based on this, the following assumptions have been made: \n" + +[[ "$VELERO_PATCH" != 0 ]] && printf "*\t This is a patch release.\n" + +# -n is "string is non-empty" +[[ -n $VELERO_PRERELEASE ]] && printf "*\t This is a pre-release.\n" + +# -z is "string is empty" +[[ -z $VELERO_PRERELEASE ]] && printf "*\t This is a GA release.\n" + +echo "If this is all correct, press enter/return to proceed to TAG THE RELEASE and UPLOAD THE TAG TO GITHUB." +echo "Otherwise, press ctrl-c to CANCEL the process without making any changes." + +read -p "Ready to continue? " + +echo "Alright, let's go." + +echo "Pulling down all git tags and branches before doing any work." +git fetch upstream --all --tags + +# If we've got a patch release, we'll need to create a release branch for it. +if [[ "$VELERO_PATCH" > 0 ]]; then + release_branch_name=release-$VELERO_MAJOR.$VELERO_MINOR + + # Check if the branch exists, creating it if not. + # The fetch command above should have gotten all the upstream branches, so we can safely assume this check is local & upstream branches. + if [[ -z $(git branch | grep $release_branch_name) ]]; then + git checkout -b $release_branch_name + echo "Release branch made." + else + echo "Release branch $release_branch_name exists already." + git checkout $release_branch_name + fi + + echo "Now you'll need to cherry-pick any relevant git commits into this release branch." + echo "Either pause this script with ctrl-z, or open a new terminal window and do the cherry-picking." + read -p "Press enter when you're done cherry-picking. THIS WILL MAKE A TAG PUSH THE BRANCH TO UPSTREAM" + + # TODO can/should we add a way to review the cherry-picked commits before the push? + + echo "Pushing $release_branch_name to upstream remote" + git push --set-upstream upstream/$release_branch_name $release_branch_name + + tag_and_push +else + echo "Checking out upstream/master." + git checkout upstream/master + + tag_and_push +fi + + + +echo "Invoking Goreleaser to create the GitHub release." +RELEASE_NOTES_FILE=changelogs/CHANGELOG-$VELERO_MAJOR.$VELERO_MINOR.md \ + PUBLISH=TRUE \ + make release diff --git a/site/docs/master/release-instructions.md b/site/docs/master/release-instructions.md index 37a484318..df8600676 100644 --- a/site/docs/master/release-instructions.md +++ b/site/docs/master/release-instructions.md @@ -54,18 +54,8 @@ You may regenerate the token for every release if you prefer. This process is the same for both pre-release and GA, except for the fact that there will not be a blog post PR to merge for pre-release versions. 1. Merge the changelog + docs PR, so that it's included in the release tag. -1. Make sure your working directory is clean: `git status` should show `nothing to commit, working tree clean`. -1. Run `git fetch upstream master && git checkout upstream/master`. -1. Run `git tag $VELERO_VERSION (e.g. `git tag v1.2.0` or `git tag v1.2.0-beta.1`). -1. Run `git push upstream $VELERO_VERSION` (e.g. `git push upstream v1.2.0` or `git push upstream v1.2.0-beta.1`). This will trigger the github action that builds/publishes the Docker images. -1. Generate the GitHub release (it will be created in "Draft" status, which means it's not visible to the outside world until you click "Publish"): - - ```bash - GITHUB_TOKEN=your-github-token \ - RELEASE_NOTES_FILE=changelogs/CHANGELOG-..md \ - PUBLISH=true \ - make release - ``` +1. Set your GitHub token as an environment variable. `export GITHUB_TOKEN=` +1. Run `/hack/release-tools/tag-release.sh` and follow the instructions. 1. Navigate to the draft GitHub release, at https://github.com/vmware-tanzu/velero/releases. 1. If this is a patch release (e.g. `v1.2.1`), note that the full `CHANGELOG-1.2.md` contents will be included in the body of the GitHub release. You need to delete the previous releases' content (e.g. `v1.2.0`'s changelog) so that only the latest patch release's changelog shows.