mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-10 08:20:59 +00:00
Refactor
This commit is contained in:
293
releasing/releasing/gitrunner.go
Normal file
293
releasing/releasing/gitrunner.go
Normal file
@@ -0,0 +1,293 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type gitRunner struct {
|
||||
// Original git repo path, which should be current working directory
|
||||
originalGitPath string
|
||||
// A temporary path for worktree
|
||||
worktreePath string
|
||||
// Does this have worktree
|
||||
hasWorktree bool
|
||||
}
|
||||
|
||||
func newGitRunner(worktree bool) (gitRunner, error) {
|
||||
gr := gitRunner{}
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return gr, err
|
||||
}
|
||||
gr.originalGitPath = pwd
|
||||
gr.hasWorktree = worktree
|
||||
if worktree {
|
||||
err = gr.CreateWorktreeDir()
|
||||
if err != nil {
|
||||
return gr, err
|
||||
}
|
||||
}
|
||||
return gr, nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) Close() error {
|
||||
if !gr.hasWorktree {
|
||||
return nil
|
||||
}
|
||||
err := gr.DeleteWorktreeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gr.PruneWorktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) DeleteWorktreeDir() error {
|
||||
logDebug("Deleting git worktree dir: %s", gr.worktreePath)
|
||||
err := os.RemoveAll(gr.worktreePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logDebug("Deleting done")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) WorktreePath() (string, error) {
|
||||
if gr.worktreePath == "" {
|
||||
return "", fmt.Errorf("Empty worktree path")
|
||||
}
|
||||
return gr.worktreePath, nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) OriginalGitPath() (string, error) {
|
||||
if gr.originalGitPath == "" {
|
||||
return "", fmt.Errorf("Empty git path")
|
||||
}
|
||||
return gr.originalGitPath, nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) CreateWorktreeDir() error {
|
||||
// Create temporary directory
|
||||
temp, err := ioutil.TempDir("", "kustomize-releases")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gr.worktreePath = filepath.Join(temp, "sigs.k8s.io/kustomize")
|
||||
err = os.MkdirAll(gr.worktreePath, 0700)
|
||||
logDebug("Created git worktree dir: %s", gr.worktreePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) CheckRemoteExistence(remote string) error {
|
||||
path, err := gr.OriginalGitPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logDebug("Checking remote %s in %s", remote, path)
|
||||
cmd := exec.Command("git", "remote")
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s", err.Error(), stdoutStderr)
|
||||
}
|
||||
logDebug("Remotes:\n%s", string(stdoutStderr))
|
||||
|
||||
regString := fmt.Sprintf("(?m)^%s$", remote)
|
||||
reg := regexp.MustCompile(regString)
|
||||
if !reg.MatchString(string(stdoutStderr)) {
|
||||
return fmt.Errorf("Cannot find remote named %s", remote)
|
||||
}
|
||||
logDebug("Remote %s exists", remote)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) FetchTags(remote string) error {
|
||||
logDebug("Fetching latest tags")
|
||||
path, err := gr.OriginalGitPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command("git", "fetch", "-t", remote)
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s", err.Error(), stdoutStderr)
|
||||
}
|
||||
logDebug("Finished fetching")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) GetTags() (string, error) {
|
||||
path, err := gr.OriginalGitPath()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
logDebug("Getting latest tag in repo %s", path)
|
||||
cmd := exec.Command("git", "tag", "-l")
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s %s", err.Error(), stdoutStderr)
|
||||
}
|
||||
logDebug("Finished getting tags")
|
||||
return string(stdoutStderr), nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) CheckBranchExistence(name string) (bool, error) {
|
||||
logDebug("Checking branch %s existence", name)
|
||||
path, err := gr.OriginalGitPath()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
cmd := exec.Command("git", "branch", "-a")
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%s %s", err.Error(), stdoutStderr)
|
||||
}
|
||||
branches := strings.Split(string(stdoutStderr), "\n")
|
||||
for _, branch := range branches {
|
||||
if strings.Trim(branch, " ") == "remotes/"+name {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) NewBranch(name string) error {
|
||||
logInfo("Creating new branch %s", name)
|
||||
path, err := gr.OriginalGitPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
upstreamBranch := "upstream/" + name
|
||||
cmd := exec.Command("git", "branch", name, upstreamBranch)
|
||||
exist, err := gr.CheckBranchExistence(upstreamBranch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
logInfo("Remote branch %s doesn't exist", upstreamBranch)
|
||||
cmd = exec.Command("git", "branch", name)
|
||||
}
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s", err.Error(), stdoutStderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) DeleteBranch(name string) error {
|
||||
logDebug("Deleting branch %s", name)
|
||||
path, err := gr.OriginalGitPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command("git", "branch", "-D", name)
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s", err.Error(), stdoutStderr)
|
||||
}
|
||||
logDebug("Finished deleting branch")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) AddWorktree(branch string) error {
|
||||
path, err := gr.OriginalGitPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tempDir, err := gr.WorktreePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
return fmt.Errorf("%s %s", err.Error(), stdoutStderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) PruneWorktree() error {
|
||||
path, err := gr.OriginalGitPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logDebug("Pruning worktree for repo %s", path)
|
||||
cmd := exec.Command("git", "worktree", "prune")
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s", err.Error(), stdoutStderr)
|
||||
}
|
||||
logDebug("Finished pruning worktree")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) Merge(branch string) error {
|
||||
logInfo("Merging %s", branch)
|
||||
path, err := gr.WorktreePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logDebug("Working dir: %s", path)
|
||||
cmd := exec.Command("git", "merge", branch)
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s", err.Error(), stdoutStderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gr *gitRunner) PushRelease(branch string, mod module) error {
|
||||
logInfo("Pushing branch %s", branch)
|
||||
path, err := gr.WorktreePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command("git", "push", "upstream", branch)
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s", err.Error(), stdoutStderr)
|
||||
}
|
||||
|
||||
logInfo("Creating tag %s", mod.Tag())
|
||||
cmd = exec.Command(
|
||||
"git", "tag",
|
||||
"-a", mod.Tag(),
|
||||
"-m", fmt.Sprintf("Release %s on branch %s", mod.Tag(), branch),
|
||||
)
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s", err.Error(), stdoutStderr)
|
||||
}
|
||||
|
||||
logInfo("Pushing tag %s", mod.Tag())
|
||||
cmd = exec.Command("git", "push", "upstream", mod.Tag())
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s", err.Error(), stdoutStderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
271
releasing/releasing/main.go
Normal file
271
releasing/releasing/main.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var modules = [...]string{
|
||||
"kyaml", "api", "kstatus", "cmd/config",
|
||||
"cmd/resource", "cmd/kubectl", "pluginator", "kustomize",
|
||||
}
|
||||
|
||||
// Enable verbose or not
|
||||
var verbose bool
|
||||
|
||||
// Disable dry run
|
||||
var noDryRun bool
|
||||
|
||||
// Enable module tests
|
||||
var doTest bool
|
||||
|
||||
// === Log helper functions ===
|
||||
|
||||
func logDebug(format string, v ...interface{}) {
|
||||
if verbose {
|
||||
log.Printf("DEBUG "+format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
func logFatalE(e error) {
|
||||
log.Fatalf("FATAL %s", e.Error())
|
||||
}
|
||||
|
||||
// === Command line commands ===
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "releasing",
|
||||
Short: "This go program is used to improve the modules releasing process in Kustomize repository.",
|
||||
}
|
||||
|
||||
func listCmdImpl() error {
|
||||
gr, err := newGitRunner(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logDebug("Working directory: %s", gr.originalGitPath)
|
||||
remote := "upstream"
|
||||
|
||||
err = gr.CheckRemoteExistence(remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gr.FetchTags(remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tags, err := gr.GetTags()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res := []string{} // Store result strings
|
||||
for _, modName := range modules {
|
||||
mod := module{
|
||||
name: modName,
|
||||
}
|
||||
err = mod.UpdateVersion(tags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res = append(res, fmt.Sprintf("%s/%s", mod.name, mod.version.String()))
|
||||
}
|
||||
err = gr.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, l := range res {
|
||||
fmt.Println(l)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var listSubCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List current version of all covered modules",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := listCmdImpl()
|
||||
if err != nil {
|
||||
logFatalE(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func checkReleaseArgs(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
|
||||
}
|
||||
|
||||
func releaseCmdImpl(args []string) error {
|
||||
modName := args[0]
|
||||
versionType := args[1]
|
||||
gr, err := newGitRunner(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logInfo("Creating tag for module %s", modName)
|
||||
logDebug("Working directory: %s", gr.originalGitPath)
|
||||
remote := "upstream"
|
||||
|
||||
err = gr.CheckRemoteExistence(remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gr.FetchTags(remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tags, err := gr.GetTags()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gitPath, err := gr.OriginalGitPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mod := module{
|
||||
name: modName,
|
||||
path: gitPath,
|
||||
}
|
||||
err = mod.UpdateVersion(tags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldVersion := mod.version.String()
|
||||
err = mod.version.Bump(versionType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
err = gr.NewBranch(branch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = gr.AddWorktree(branch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = gr.Merge("upstream/master")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Update module path
|
||||
worktreePath, err := gr.WorktreePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mod.path = worktreePath
|
||||
|
||||
logInfo(
|
||||
"Releasing summary:\nDir:\t%s\nModule:\t%s %s\nBranch:\t%s\nTag:\t%s",
|
||||
worktreePath,
|
||||
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 {
|
||||
err = gr.PushRelease(branch, mod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Clean
|
||||
err = gr.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gr.DeleteBranch(branch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logInfo("Releasing for module %s completes", mod.name)
|
||||
return nil
|
||||
}
|
||||
|
||||
var release = &cobra.Command{
|
||||
Use: "release [module name] [version type]",
|
||||
Short: "Release a new version of specified module",
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
return checkReleaseArgs(args)
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := releaseCmdImpl(args)
|
||||
if err != nil {
|
||||
logFatalE(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var subCmds = [...]*cobra.Command{
|
||||
listSubCmd,
|
||||
release,
|
||||
}
|
||||
|
||||
// === Main function ===
|
||||
|
||||
func main() {
|
||||
for _, cmd := range subCmds {
|
||||
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(&doTest, "do-test", "", false, "run module tests before releasing")
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
logFatal(err.Error())
|
||||
}
|
||||
}
|
||||
20
releasing/releasing/main_test.go
Normal file
20
releasing/releasing/main_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
err := listCmdImpl()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRelease(t *testing.T) {
|
||||
args := []string{"api", "minor"}
|
||||
err := releaseCmdImpl(args)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
42
releasing/releasing/modulemeta.go
Normal file
42
releasing/releasing/modulemeta.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
name string
|
||||
path string
|
||||
version moduleVersion
|
||||
}
|
||||
|
||||
func (m *module) UpdateVersion(tags string) error {
|
||||
v, err := newModuleVersionFromGitTags(tags, m.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.version = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *module) Tag() string {
|
||||
return m.name + "/" + m.version.String()
|
||||
}
|
||||
|
||||
func (m *module) RunTest() (string, error) {
|
||||
if !doTest {
|
||||
logInfo("Tests disabled.")
|
||||
return "", nil
|
||||
}
|
||||
testPath := filepath.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
|
||||
}
|
||||
93
releasing/releasing/moduleversion.go
Normal file
93
releasing/releasing/moduleversion.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type moduleVersion struct {
|
||||
major int
|
||||
minor int
|
||||
patch int
|
||||
}
|
||||
|
||||
func (v *moduleVersion) String() string {
|
||||
return fmt.Sprintf("v%d.%d.%d", v.major, v.minor, v.patch)
|
||||
}
|
||||
|
||||
func (v *moduleVersion) Bump(t string) error {
|
||||
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 {
|
||||
return fmt.Errorf("Invalid version type: %s", t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newModuleVersionFromString(vs string) (*moduleVersion, error) {
|
||||
if len(vs) < 1 {
|
||||
return nil, fmt.Errorf("Invalid version string %s", vs)
|
||||
}
|
||||
if vs[0] == 'v' {
|
||||
vs = vs[1:]
|
||||
}
|
||||
versions := strings.Split(vs, ".")
|
||||
major, err := strconv.Atoi(versions[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
minor, err := strconv.Atoi(versions[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patch, err := strconv.Atoi(versions[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := moduleVersion{
|
||||
major: major,
|
||||
minor: minor,
|
||||
patch: patch,
|
||||
}
|
||||
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func newModuleVersionFromGitTags(tags, modName string) (moduleVersion, error) {
|
||||
// Search for module tag
|
||||
regString := fmt.Sprintf("(?m)^%s/v(\\d+\\.){2}\\d+$", modName)
|
||||
reg := regexp.MustCompile(regString)
|
||||
modTagsString := reg.FindAllString(tags, -1)
|
||||
logDebug("Tags for module %s:\n%s", modName, modTagsString)
|
||||
var versions []moduleVersion
|
||||
for _, tag := range modTagsString {
|
||||
tag = tag[len(modName)+2:]
|
||||
v, err := newModuleVersionFromString(tag)
|
||||
if err != nil {
|
||||
return moduleVersion{}, err
|
||||
}
|
||||
|
||||
versions = append(versions, *v)
|
||||
}
|
||||
// Sort to find latest tag
|
||||
sort.Slice(versions, func(i, j int) bool {
|
||||
if versions[i].major == versions[j].major && versions[i].minor == versions[j].minor {
|
||||
return versions[i].patch > versions[j].patch
|
||||
} else if versions[i].major == versions[j].major {
|
||||
return versions[i].minor > versions[j].minor
|
||||
} else {
|
||||
return versions[i].major > versions[j].major
|
||||
}
|
||||
})
|
||||
return versions[0], nil
|
||||
}
|
||||
@@ -1,478 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var modules = [...]string{
|
||||
"kyaml", "api", "kstatus", "cmd/config",
|
||||
"cmd/resource", "cmd/kubectl", "pluginator", "kustomize",
|
||||
}
|
||||
var verbose bool // Enable verbose or not
|
||||
var noDryRun bool // Disable dry run
|
||||
var doTest bool // Enable module tests
|
||||
var tempDir string // Temporary directory path for git worktree
|
||||
|
||||
// === Log helper functions ===
|
||||
|
||||
func logDebug(format string, v ...interface{}) {
|
||||
if verbose {
|
||||
log.Printf("DEBUG "+format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
// === Command line commands ===
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "releasing",
|
||||
Short: "This go program is used to improve the modules releasing process in Kustomize repository.",
|
||||
}
|
||||
|
||||
var listSubCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List current version of all covered modules",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
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)
|
||||
res := []string{} // Store result strings
|
||||
for _, mod := range modules {
|
||||
res = append(res, fmt.Sprintf("%s/%s", mod, getModuleCurrentVersion(mod, pwd)))
|
||||
}
|
||||
for _, l := range res {
|
||||
fmt.Println(l)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var release = &cobra.Command{
|
||||
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
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
modName := args[0]
|
||||
versionType := args[1]
|
||||
createTempDir()
|
||||
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 {
|
||||
pushRelease(tempDir, branch, mod)
|
||||
}
|
||||
// Clean
|
||||
removeTempDir()
|
||||
pruneWorktree(pwd)
|
||||
deleteBranch(pwd, branch)
|
||||
logInfo("Releasing for module %s completes", mod.name)
|
||||
},
|
||||
}
|
||||
|
||||
var subCmds = [...]*cobra.Command{
|
||||
listSubCmd,
|
||||
release,
|
||||
}
|
||||
|
||||
// === Main function ===
|
||||
|
||||
func main() {
|
||||
for _, cmd := range subCmds {
|
||||
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(&doTest, "do-test", "", false, "run module tests before releasing")
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
logFatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// === module version struct and functions definition ===
|
||||
|
||||
type moduleVersion struct {
|
||||
major int
|
||||
minor int
|
||||
patch int
|
||||
}
|
||||
|
||||
func (v moduleVersion) String() string {
|
||||
return fmt.Sprintf("v%d.%d.%d", v.major, v.minor, v.patch)
|
||||
}
|
||||
|
||||
func (v *moduleVersion) Set(major, minor, patch int) {
|
||||
v.major = major
|
||||
v.minor = minor
|
||||
v.patch = patch
|
||||
}
|
||||
|
||||
func (v *moduleVersion) FromString(vs string) {
|
||||
versions := strings.Split(vs, ".")
|
||||
major, err := strconv.Atoi(versions[0])
|
||||
if err != nil {
|
||||
logFatal(err.Error())
|
||||
}
|
||||
minor, err := strconv.Atoi(versions[1])
|
||||
if err != nil {
|
||||
logFatal(err.Error())
|
||||
}
|
||||
patch, err := strconv.Atoi(versions[2])
|
||||
if err != nil {
|
||||
logFatal(err.Error())
|
||||
}
|
||||
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 {
|
||||
name string
|
||||
path string
|
||||
version moduleVersion
|
||||
}
|
||||
|
||||
func (m *module) UpdateCurrentVersion() {
|
||||
logDebug("Getting latest tag for %s", m.name)
|
||||
cmd := exec.Command("git", "tag", "-l")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Dir = m.path
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
logFatal(err.Error())
|
||||
}
|
||||
|
||||
// Search for module tag
|
||||
regString := fmt.Sprintf("(?m)^%s/v(\\d+\\.){2}\\d+$", m.name)
|
||||
reg := regexp.MustCompile(regString)
|
||||
tagsString := reg.FindAllString(out.String(), -1)
|
||||
logDebug("Tags for module %s:\n%s", m.name, tagsString)
|
||||
var versions []moduleVersion
|
||||
for _, tag := range tagsString {
|
||||
tag = tag[len(m.name)+2:]
|
||||
v := moduleVersion{}
|
||||
v.FromString(tag)
|
||||
|
||||
versions = append(versions, v)
|
||||
}
|
||||
// Sort to find latest tag
|
||||
sort.Slice(versions, func(i, j int) bool {
|
||||
if versions[i].major == versions[j].major && versions[i].minor == versions[j].minor {
|
||||
return versions[i].patch > versions[j].patch
|
||||
} else if versions[i].major == versions[j].major {
|
||||
return versions[i].minor > versions[j].minor
|
||||
} else {
|
||||
return versions[i].major > versions[j].major
|
||||
}
|
||||
})
|
||||
|
||||
m.version = versions[0]
|
||||
}
|
||||
|
||||
func (m *module) Tag() string {
|
||||
return m.name + "/" + m.version.String()
|
||||
}
|
||||
|
||||
func (m module) RunTest() (string, error) {
|
||||
if !doTest {
|
||||
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 createTempDir() {
|
||||
// Create temporary directory
|
||||
temp, err := ioutil.TempDir("", "kustomize-releases")
|
||||
if err != nil {
|
||||
logFatal(err.Error())
|
||||
}
|
||||
logDebug("Created git temp dir: " + tempDir)
|
||||
tempDir = path.Join(temp, "sigs.k8s.io/kustomize")
|
||||
err = os.MkdirAll(tempDir, 0700)
|
||||
if err != nil {
|
||||
logFatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func removeTempDir() {
|
||||
logDebug("Deleting git temp dir: " + tempDir)
|
||||
err := os.RemoveAll(tempDir)
|
||||
if err != nil {
|
||||
logFatal(err.Error())
|
||||
}
|
||||
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))
|
||||
}
|
||||
branches := strings.Split(string(stdoutStderr), "\n")
|
||||
for _, branch := range branches {
|
||||
if strings.Trim(branch, " ") == "remotes/"+name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
func pushRelease(path, branch string, mod module) {
|
||||
logInfo("Pushing branch %s", branch)
|
||||
cmd := exec.Command("git", "push", "upstream", branch)
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
logFatal(string(stdoutStderr))
|
||||
}
|
||||
|
||||
logInfo("Creating tag %s", mod.Tag())
|
||||
cmd = exec.Command(
|
||||
"git", "tag",
|
||||
"-a", mod.Tag(),
|
||||
"-m", fmt.Sprintf("Release %s on branch %s", mod.Tag(), branch),
|
||||
)
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
logFatal(string(stdoutStderr))
|
||||
}
|
||||
|
||||
logInfo("Pushing tag %s", mod.Tag())
|
||||
cmd = exec.Command("git", "push", "upstream", mod.Tag())
|
||||
cmd.Dir = path
|
||||
stdoutStderr, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
logFatal(string(stdoutStderr))
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
remote := "upstream"
|
||||
// Check remotes
|
||||
checkRemoteExistence(pwd, remote)
|
||||
// Fetch latest tags from remote
|
||||
fetchTags(pwd, remote)
|
||||
for _, mod := range modules {
|
||||
v := getModuleCurrentVersion(mod, pwd)
|
||||
valid, err := regexp.MatchString("^v(\\d+\\.){2}\\d+$", v)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if !valid {
|
||||
t.Errorf("Returned version %s is not valid", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRelease(t *testing.T) {
|
||||
createTempDir()
|
||||
modName := "api"
|
||||
versionType := "patch"
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
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
|
||||
|
||||
// Clean
|
||||
removeTempDir()
|
||||
pruneWorktree(pwd)
|
||||
deleteBranch(pwd, branch)
|
||||
}
|
||||
Reference in New Issue
Block a user