This commit is contained in:
Donny Xia
2020-05-20 12:53:05 -07:00
parent 2b0b29aec5
commit 2df3a7fc08
7 changed files with 719 additions and 548 deletions

View 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
View 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())
}
}

View 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)
}
}

View 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
}

View 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
}

View File

@@ -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))
}
}

View File

@@ -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)
}