Add release command

This commit is contained in:
Donny Xia
2020-05-18 14:08:40 -07:00
parent 81da8f6f99
commit 650f111e63
4 changed files with 245 additions and 59 deletions

View File

@@ -2,7 +2,4 @@ module sigs.k8s.io/kustomize/releasing
go 1.13
require (
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/spf13/cobra v1.0.0
)
require github.com/spf13/cobra v1.0.0

View File

@@ -40,6 +40,7 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=

View File

@@ -2,11 +2,13 @@ package main
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"regexp"
"sort"
"strconv"
@@ -20,8 +22,9 @@ var modules = [...]string{
"cmd/resource", "cmd/kubectl", "pluginator", "kustomize",
}
var verbose bool // Enable verbose or not
var noDryRun bool // Disable dry run
var noTest bool // Disable module tests
var tempDir string // Temporary directory path for git worktree
var pwd string // Current working directory
// === Log helper functions ===
@@ -35,6 +38,10 @@ func logInfo(format string, v ...interface{}) {
log.Printf("INFO "+format, v...)
}
func logWarn(format string, v ...interface{}) {
log.Printf("WARN "+format, v...)
}
func logFatal(format string, v ...interface{}) {
log.Fatalf("FATAL "+format, v...)
}
@@ -50,8 +57,7 @@ var listSubCmd = &cobra.Command{
Use: "list",
Short: "List current version of all covered modules",
Run: func(cmd *cobra.Command, args []string) {
var err error
pwd, err = os.Getwd()
pwd, err := os.Getwd()
if err != nil {
logFatal(err.Error())
}
@@ -63,7 +69,7 @@ var listSubCmd = &cobra.Command{
fetchTags(pwd, remote)
res := []string{} // Store result strings
for _, mod := range modules {
res = append(res, fmt.Sprintf("%s/%s", mod, getModuleCurrentVersion(mod)))
res = append(res, fmt.Sprintf("%s/%s", mod, getModuleCurrentVersion(mod, pwd)))
}
for _, l := range res {
fmt.Println(l)
@@ -72,18 +78,98 @@ var listSubCmd = &cobra.Command{
}
var release = &cobra.Command{
Use: "release",
Use: "release [module name] [version type]",
Short: "Release a new version of specified module",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 2 {
return errors.New("2 arguments are required")
}
found := false
for _, mod := range modules {
if mod == args[0] {
found = true
break
}
}
if !found {
return fmt.Errorf("%s is not a valid module. Valid modules are %s", args[0], modules)
}
types := []string{"major", "minor", "patch"}
found = false
for _, t := range types {
if t == args[1] {
found = true
break
}
}
if !found {
return fmt.Errorf("%s is not a valid version type. Valid types are %s", args[1], types)
}
return nil
},
PreRun: func(cmd *cobra.Command, args []string) {
logDebug("Preparing Git environemnt")
prepareGit()
},
Run: func(cmd *cobra.Command, args []string) {
logInfo("Done")
},
PostRun: func(cmd *cobra.Command, args []string) {
logDebug("Cleaning Git environment")
modName := args[0]
versionType := args[1]
logInfo("Creating tag for module %s", modName)
pwd, err := os.Getwd()
if err != nil {
logFatal(err.Error())
}
logDebug("Working directory: %s", pwd)
remote := "upstream"
// Check remotes
checkRemoteExistence(pwd, remote)
// Fetch latest tags from remote
fetchTags(pwd, remote)
mod := module{
name: modName,
path: pwd,
}
mod.UpdateCurrentVersion()
oldVersion := mod.version.String()
mod.version.Bump(versionType)
newVersion := mod.version.String()
logInfo("Bumping version: %s => %s", oldVersion, newVersion)
// Create branch
branch := fmt.Sprintf("release-%s-v%d.%d", mod.name, mod.version.major, mod.version.minor)
newBranch(pwd, branch)
addWorktree(pwd, tempDir, branch)
merge(tempDir, "upstream/master")
// Update module path
mod.path = tempDir
logInfo(
"Releasing summary:\nDir:\t%s\nModule:\t%s %s\nBranch:\t%s\nTag:\t%s",
tempDir,
mod.name,
mod.version.String(),
branch,
mod.Tag(),
)
// Run module tests
output, err := mod.RunTest()
if err != nil {
logWarn(output)
} else if !noDryRun {
logInfo("Skipping push module %s. Run with --no-dry-run to push the release.", mod.name)
} else {
// TODO: Push tags
}
// Clean
cleanGit()
pruneWorktree(pwd)
deleteBranch(pwd, branch)
logInfo("Done")
},
}
@@ -99,51 +185,25 @@ func main() {
rootCmd.AddCommand(cmd)
}
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
release.Flags().BoolVarP(&noDryRun, "no-dry-run", "", false, "disable dry-run")
release.Flags().BoolVarP(&noTest, "no-test", "", false, "don't run module tests")
if err := rootCmd.Execute(); err != nil {
logFatal(err.Error())
}
}
func getModuleCurrentVersion(modName string) string {
mod := newModule(modName, pwd)
func getModuleCurrentVersion(modName, path string) string {
mod := module{
name: modName,
path: path,
}
mod.UpdateCurrentVersion()
v := mod.version.String()
logDebug("module %s version.toString => %s", mod.name, v)
return v
}
func checkRemoteExistence(path string, remote string) {
logDebug("Checking remote %s in %s", remote, path)
cmd := exec.Command("git", "remote")
cmd.Dir = path
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
logFatal(err.Error())
}
logDebug("Remotes:\n%s", out.String())
regString := fmt.Sprintf("(?m)^%s$", remote)
reg := regexp.MustCompile(regString)
if !reg.MatchString(out.String()) {
logFatal("Cannot find remote named %s", remote)
}
logDebug("Remote %s exists", remote)
}
func fetchTags(path string, remote string) {
logDebug("Fetching latest tags")
cmd := exec.Command("git", "fetch", "-t", remote)
cmd.Dir = path
err := cmd.Run()
if err != nil {
logFatal(err.Error())
}
logDebug("Finished fetching")
}
// === module version struct and functions definition ===
type moduleVersion struct {
@@ -156,7 +216,7 @@ func (v moduleVersion) String() string {
return fmt.Sprintf("v%d.%d.%d", v.major, v.minor, v.patch)
}
func (v *moduleVersion) Set(major int, minor int, patch int) {
func (v *moduleVersion) Set(major, minor, patch int) {
v.major = major
v.minor = minor
v.patch = patch
@@ -179,6 +239,21 @@ func (v *moduleVersion) FromString(vs string) {
v.Set(major, minor, patch)
}
func (v *moduleVersion) Bump(t string) {
if t == "major" {
v.major++
v.minor = 0
v.patch = 0
} else if t == "minor" {
v.minor++
v.patch = 0
} else if t == "patch" {
v.patch++
} else {
logFatal("Invalid version type: %s", t)
}
}
// === module struct and functions definition ===
type module struct {
@@ -187,15 +262,6 @@ type module struct {
version moduleVersion
}
func newModule(modName string, path string) module {
mod := module{
name: modName,
path: path,
}
logDebug("Created module struct for %s", modName)
return mod
}
func (m *module) UpdateCurrentVersion() {
logDebug("Getting latest tag for %s", m.name)
cmd := exec.Command("git", "tag", "-l")
@@ -234,10 +300,32 @@ func (m *module) UpdateCurrentVersion() {
m.version = versions[0]
}
func (m *module) Tag() string {
return m.name + "/" + m.version.String()
}
func (m *module) RunTest() (string, error) {
if noTest {
logInfo("Tests disabled.")
return "", nil
}
testPath := path.Join(m.path, m.name)
logInfo("Running tests in %s...", testPath)
cmd := exec.Command("go", "test", "./...")
cmd.Dir = testPath
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
return string(stdoutStderr), err
}
logInfo("Tests are successfully finished")
return "", nil
}
// === Git environment functions ===
func prepareGit() {
var err error
// Create temporary directory
tempDir, err = ioutil.TempDir("", "kustomize-releases")
if err != nil {
logFatal(err.Error())
@@ -253,3 +341,104 @@ func cleanGit() {
}
logDebug("Deleting done")
}
func checkRemoteExistence(path, remote string) {
logDebug("Checking remote %s in %s", remote, path)
cmd := exec.Command("git", "remote")
cmd.Dir = path
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
logFatal(string(stdoutStderr))
}
logDebug("Remotes:\n%s", string(stdoutStderr))
regString := fmt.Sprintf("(?m)^%s$", remote)
reg := regexp.MustCompile(regString)
if !reg.MatchString(string(stdoutStderr)) {
logFatal("Cannot find remote named %s", remote)
}
logDebug("Remote %s exists", remote)
}
func fetchTags(path, remote string) {
logDebug("Fetching latest tags")
cmd := exec.Command("git", "fetch", "-t", remote)
cmd.Dir = path
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
logFatal(string(stdoutStderr))
}
logDebug("Finished fetching")
}
func checkBranchExistence(path, name string) bool {
logDebug("Checking branch %s existence", name)
cmd := exec.Command("git", "branch", "-a")
cmd.Dir = path
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
logFatal(string(stdoutStderr))
}
return strings.Contains(string(stdoutStderr), name)
}
func newBranch(path, name string) {
logInfo("Creating new branch %s", name)
upstreamBranch := "upstream/" + name
cmd := exec.Command("git", "branch", name, upstreamBranch)
if !checkBranchExistence(path, upstreamBranch) {
logInfo("Remote branch %s doesn't exist", upstreamBranch)
cmd = exec.Command("git", "branch", name)
}
cmd.Dir = path
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
logFatal(string(stdoutStderr))
}
logInfo("Finished creating branch")
}
func deleteBranch(path, name string) {
logDebug("Deleting branch %s", name)
cmd := exec.Command("git", "branch", "-D", name)
cmd.Dir = path
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
logFatal(string(stdoutStderr))
}
logDebug("Finished deleting branch")
}
func addWorktree(path, tempDir, branch string) {
logInfo("Adding worktree %s for branch %s", tempDir, branch)
cmd := exec.Command("git", "worktree", "add", tempDir, branch)
cmd.Dir = path
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
logFatal(string(stdoutStderr))
}
logInfo("Finished adding worktree")
}
func pruneWorktree(path string) {
logDebug("Pruning worktree for repo %s", path)
cmd := exec.Command("git", "worktree", "prune")
cmd.Dir = path
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
logFatal(string(stdoutStderr))
}
logDebug("Finished pruning worktree")
}
func merge(path, branch string) {
logInfo("Merging %s", branch)
logDebug("Working dir: %s", path)
cmd := exec.Command("git", "merge", branch)
cmd.Dir = path
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
logFatal(string(stdoutStderr))
}
logInfo("Finished merging")
}

View File

@@ -7,8 +7,7 @@ import (
)
func TestGetModuleCurrentVersion(t *testing.T) {
var err error
pwd, err = os.Getwd()
pwd, err := os.Getwd()
if err != nil {
t.Errorf(err.Error())
}
@@ -18,7 +17,7 @@ func TestGetModuleCurrentVersion(t *testing.T) {
// Fetch latest tags from remote
fetchTags(pwd, remote)
for _, mod := range modules {
v := getModuleCurrentVersion(mod)
v := getModuleCurrentVersion(mod, pwd)
valid, err := regexp.MatchString("^v(\\d+\\.){2}\\d+$", v)
if err != nil {
t.Errorf(err.Error())