mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-12 01:14:22 +00:00
Replace bash release helper scripts with Go progam
This commit is contained in:
134
cmd/gorepomod/internal/repo/dotgitdata.go
Normal file
134
cmd/gorepomod/internal/repo/dotgitdata.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/git"
|
||||
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/misc"
|
||||
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
dotGitFileName = ".git"
|
||||
srcHint = "/src/"
|
||||
goModFile = "go.mod"
|
||||
)
|
||||
|
||||
// DotGitData holds basic information about a local .git file
|
||||
type DotGitData struct {
|
||||
// srcPath is the absolute path to the local Go src directory.
|
||||
// This used to be $GOPATH/src.
|
||||
// It's the directory containing git repository clones.
|
||||
srcPath string
|
||||
// The path below srcPath to a particular repository
|
||||
// directory, a directory containing a .git directory.
|
||||
// Typically {repoOrg}/{repoUserName}, e.g. sigs.k8s.io/cli-utils
|
||||
repoPath string
|
||||
}
|
||||
|
||||
func (dg *DotGitData) SrcPath() string {
|
||||
return dg.srcPath
|
||||
}
|
||||
|
||||
func (dg *DotGitData) RepoPath() string {
|
||||
return dg.repoPath
|
||||
}
|
||||
|
||||
func (dg *DotGitData) AbsPath() string {
|
||||
return filepath.Join(dg.srcPath, dg.repoPath)
|
||||
}
|
||||
|
||||
// NewDotGitDataFromPath wants the incoming path to hold dotGit
|
||||
// E.g.
|
||||
// ~/gopath/src/sigs.k8s.io/kustomize
|
||||
// ~/gopath/src/github.com/monopole/gorepomod
|
||||
func NewDotGitDataFromPath(path string) (*DotGitData, error) {
|
||||
if !utils.DirExists(filepath.Join(path, dotGitFileName)) {
|
||||
return nil, fmt.Errorf(
|
||||
"%q doesn't have a %q file", path, dotGitFileName)
|
||||
}
|
||||
// This is an attempt to figure out where the user has cloned
|
||||
// their repos. In the old days, it was an import path under
|
||||
// $GOPATH/src. If we cannot guess it, we may need to ask for it,
|
||||
// or maybe proceed without knowing it.
|
||||
index := strings.Index(path, srcHint)
|
||||
if index < 0 {
|
||||
return nil, fmt.Errorf(
|
||||
"path %q doesn't contain %q", path, srcHint)
|
||||
}
|
||||
return &DotGitData{
|
||||
srcPath: path[:index+len(srcHint)-1],
|
||||
repoPath: path[index+len(srcHint):],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// It's a factory factory.
|
||||
func (dg *DotGitData) NewRepoFactory(
|
||||
exclusions []string) (*ManagerFactory, error) {
|
||||
modules, err := loadProtoModules(dg.AbsPath(), exclusions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = dg.checkModules(modules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runner := git.NewQuiet(dg.AbsPath(), true)
|
||||
remoteName, err := runner.DetermineRemoteToUse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Some tags might exist for modules that
|
||||
// have been renamed or deleted; ignore those.
|
||||
// There might be newer tags locally than remote,
|
||||
// so report both.
|
||||
localTags, err := runner.LoadLocalTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remoteTags, err := runner.LoadRemoteTags(remoteName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ManagerFactory{
|
||||
dg: dg,
|
||||
modules: modules,
|
||||
remoteName: remoteName,
|
||||
versionMapLocal: localTags,
|
||||
versionMapRemote: remoteTags,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (dg *DotGitData) checkModules(modules []*protoModule) error {
|
||||
for _, pm := range modules {
|
||||
|
||||
file := filepath.Join(pm.PathToGoMod(), goModFile)
|
||||
|
||||
// Do the paths make sense?
|
||||
if !strings.HasPrefix(pm.FullPath(), dg.RepoPath()) {
|
||||
return fmt.Errorf(
|
||||
"module %q doesn't start with the repository name %q",
|
||||
pm.FullPath(), dg.RepoPath())
|
||||
}
|
||||
|
||||
shortName := pm.ShortName(dg.RepoPath())
|
||||
if shortName == misc.ModuleAtTop {
|
||||
if pm.PathToGoMod() != dg.AbsPath() {
|
||||
return fmt.Errorf("in %q, problem with top module", file)
|
||||
}
|
||||
} else {
|
||||
// Do the relative path and short name make sense?
|
||||
if !strings.HasSuffix(pm.PathToGoMod(), string(shortName)) {
|
||||
return fmt.Errorf(
|
||||
"in %q, the module name %q doesn't match the file's pathToGoMod %q",
|
||||
file, shortName, pm.PathToGoMod())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
6
cmd/gorepomod/internal/repo/dotgitdata_test.go
Normal file
6
cmd/gorepomod/internal/repo/dotgitdata_test.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package repo
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLoadTags(t *testing.T) {
|
||||
}
|
||||
194
cmd/gorepomod/internal/repo/manager.go
Normal file
194
cmd/gorepomod/internal/repo/manager.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/edit"
|
||||
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/git"
|
||||
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/misc"
|
||||
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/semver"
|
||||
)
|
||||
|
||||
// Manager manages a git repo.
|
||||
// All data already loaded and validated, it's ready to go.
|
||||
type Manager struct {
|
||||
// Underlying file system facts.
|
||||
dg *DotGitData
|
||||
|
||||
// The remote used for fetching tags, pushing tags,
|
||||
// and pushing release branches.
|
||||
remoteName misc.TrackedRepo
|
||||
|
||||
// The list of known Go modules in the repo.
|
||||
modules misc.LesModules
|
||||
}
|
||||
|
||||
func (mgr *Manager) AbsPath() string {
|
||||
return mgr.dg.AbsPath()
|
||||
}
|
||||
|
||||
func (mgr *Manager) RepoPath() string {
|
||||
return mgr.dg.RepoPath()
|
||||
}
|
||||
|
||||
func (mgr *Manager) FindModule(
|
||||
target misc.ModuleShortName) misc.LaModule {
|
||||
return mgr.modules.Find(target)
|
||||
}
|
||||
|
||||
func (mgr *Manager) Tidy(doIt bool) error {
|
||||
return mgr.modules.Apply(func(m misc.LaModule) error {
|
||||
return edit.New(m, doIt).Tidy()
|
||||
})
|
||||
}
|
||||
|
||||
func (mgr *Manager) Pin(
|
||||
doIt bool, target misc.LaModule, newV semver.SemVer) error {
|
||||
return mgr.modules.Apply(func(m misc.LaModule) error {
|
||||
if yes, oldVersion := m.DependsOn(target); yes {
|
||||
return edit.New(m, doIt).Pin(target, oldVersion, newV)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (mgr *Manager) UnPin(doIt bool, target misc.LaModule) error {
|
||||
return mgr.modules.Apply(func(m misc.LaModule) error {
|
||||
if yes, oldVersion := m.DependsOn(target); yes {
|
||||
return edit.New(m, doIt).UnPin(target, oldVersion)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func hasUnPinnedDeps(m misc.LaModule) string {
|
||||
if len(m.GetReplacements()) > 0 {
|
||||
return "yes"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (mgr *Manager) List() error {
|
||||
fmt.Printf(" src path: %s\n", mgr.dg.SrcPath())
|
||||
fmt.Printf(" repo path: %s\n", mgr.RepoPath())
|
||||
fmt.Printf(" remote: %s\n", mgr.remoteName)
|
||||
format := "%-" +
|
||||
strconv.Itoa(mgr.modules.LenLongestName()+2) +
|
||||
"s%-11s%-11s%17s %s\n"
|
||||
fmt.Printf(
|
||||
format, "NAME", "LOCAL", "REMOTE",
|
||||
"HAS-UNPINNED-DEPS", "INTRA-REPO-DEPENDENCIES")
|
||||
return mgr.modules.Apply(func(m misc.LaModule) error {
|
||||
fmt.Printf(
|
||||
format, m.ShortName(),
|
||||
m.VersionLocal().Pretty(),
|
||||
m.VersionRemote().Pretty(),
|
||||
hasUnPinnedDeps(m),
|
||||
mgr.modules.InternalDeps(m))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func determineBranchAndTag(
|
||||
m misc.LaModule, v semver.SemVer) (string, string) {
|
||||
if m.ShortName() == misc.ModuleAtTop {
|
||||
return fmt.Sprintf("release-%s", v.BranchLabel()), v.String()
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"release-%s-%s", m.ShortName(), v.BranchLabel()),
|
||||
string(m.ShortName()) + "/" + v.String()
|
||||
}
|
||||
|
||||
func (mgr *Manager) Debug(_ misc.LaModule, doIt bool) error {
|
||||
gr := git.NewLoud(mgr.AbsPath(), doIt)
|
||||
return gr.Debug(mgr.remoteName)
|
||||
}
|
||||
|
||||
// Release supports a gitlab flow style release process.
|
||||
//
|
||||
// * All development happens in the branch named "master".
|
||||
// * Each minor release gets its own branch.
|
||||
// *
|
||||
func (mgr *Manager) Release(
|
||||
target misc.LaModule, bump semver.SvBump, doIt bool) error {
|
||||
|
||||
if reps := target.GetReplacements(); len(reps) > 0 {
|
||||
return fmt.Errorf(
|
||||
"to release %q, first pin these replacements: %v",
|
||||
target.ShortName(), reps)
|
||||
}
|
||||
|
||||
newVersion := target.VersionLocal().Bump(bump)
|
||||
|
||||
if newVersion.Equals(target.VersionRemote()) {
|
||||
return fmt.Errorf(
|
||||
"version %s already exists on remote - delete it first", newVersion)
|
||||
}
|
||||
if newVersion.LessThan(target.VersionRemote()) {
|
||||
fmt.Printf(
|
||||
"version %s is less than the most recent remote version (%s)",
|
||||
newVersion, target.VersionRemote())
|
||||
}
|
||||
|
||||
gr := git.NewLoud(mgr.AbsPath(), doIt)
|
||||
|
||||
relBranch, relTag := determineBranchAndTag(target, newVersion)
|
||||
|
||||
fmt.Printf(
|
||||
"Releasing %s, stepping from %s to %s\n",
|
||||
target.ShortName(), target.VersionLocal(), newVersion)
|
||||
|
||||
if err := gr.AssureCleanWorkspace(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gr.FetchRemote(mgr.remoteName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gr.CheckoutMainBranch(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gr.MergeFromRemoteMain(mgr.remoteName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gr.AssureCleanWorkspace(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gr.CheckoutReleaseBranch(mgr.remoteName, relBranch); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gr.MergeFromRemoteMain(mgr.remoteName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gr.PushBranchToRemote(mgr.remoteName, relBranch); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gr.CreateLocalReleaseTag(relTag, relBranch); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gr.PushTagToRemote(mgr.remoteName, relTag); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gr.CheckoutMainBranch(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mgr *Manager) UnRelease(target misc.LaModule, doIt bool) error {
|
||||
fmt.Printf(
|
||||
"Unreleasing %s/%s\n",
|
||||
target.ShortName(), target.VersionRemote())
|
||||
|
||||
_, tag := determineBranchAndTag(target, target.VersionRemote())
|
||||
|
||||
gr := git.NewLoud(mgr.AbsPath(), doIt)
|
||||
|
||||
if err := gr.DeleteTagFromRemote(mgr.remoteName, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gr.DeleteLocalTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
35
cmd/gorepomod/internal/repo/managerfactory.go
Normal file
35
cmd/gorepomod/internal/repo/managerfactory.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/misc"
|
||||
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/mod"
|
||||
)
|
||||
|
||||
// ManagerFactory is a collection of clean data needed to build
|
||||
// clean, fully wired up instances of Manager.
|
||||
type ManagerFactory struct {
|
||||
dg *DotGitData
|
||||
modules []*protoModule
|
||||
remoteName misc.TrackedRepo
|
||||
versionMapLocal misc.VersionMap
|
||||
versionMapRemote misc.VersionMap
|
||||
}
|
||||
|
||||
func (mf *ManagerFactory) NewRepoManager() *Manager {
|
||||
result := &Manager{
|
||||
dg: mf.dg,
|
||||
remoteName: mf.remoteName,
|
||||
}
|
||||
var modules misc.LesModules
|
||||
for _, pm := range mf.modules {
|
||||
shortName := pm.ShortName(mf.dg.RepoPath())
|
||||
modules = append(
|
||||
modules,
|
||||
mod.New(
|
||||
result, shortName, pm.mf,
|
||||
mf.versionMapLocal.Latest(shortName),
|
||||
mf.versionMapRemote.Latest(shortName)))
|
||||
}
|
||||
result.modules = modules
|
||||
return result
|
||||
}
|
||||
101
cmd/gorepomod/internal/repo/protomodule.go
Normal file
101
cmd/gorepomod/internal/repo/protomodule.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"golang.org/x/mod/modfile"
|
||||
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/misc"
|
||||
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
dotDir = "."
|
||||
)
|
||||
|
||||
// protoModule holds parts being collected to represent a module.
|
||||
type protoModule struct {
|
||||
pathToGoMod string
|
||||
mf *modfile.File
|
||||
}
|
||||
|
||||
func (pm *protoModule) FullPath() string {
|
||||
return pm.mf.Module.Mod.Path
|
||||
}
|
||||
|
||||
func (pm *protoModule) PathToGoMod() string {
|
||||
return pm.pathToGoMod
|
||||
}
|
||||
|
||||
// Represents the trailing version label in a module name.
|
||||
// See https://blog.golang.org/v2-go-modules
|
||||
var trailingVersionPattern = regexp.MustCompile("/v\\d+$")
|
||||
|
||||
func (pm *protoModule) ShortName(
|
||||
repoImportPath string) misc.ModuleShortName {
|
||||
fp := pm.FullPath()
|
||||
if fp == repoImportPath {
|
||||
return misc.ModuleAtTop
|
||||
}
|
||||
p := fp[len(repoImportPath)+1:]
|
||||
stripped := trailingVersionPattern.ReplaceAllString(p, "")
|
||||
return misc.ModuleShortName(stripped)
|
||||
}
|
||||
|
||||
func loadProtoModules(
|
||||
repoRoot string, exclusions []string) (result []*protoModule, err error) {
|
||||
var paths []string
|
||||
paths, err = getPathsToModules(repoRoot, exclusions)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, p := range paths {
|
||||
var pm *protoModule
|
||||
pm, err = loadProtoModule(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
result = append(result, pm)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func loadProtoModule(path string) (*protoModule, error) {
|
||||
mPath := filepath.Join(path, goModFile)
|
||||
content, err := ioutil.ReadFile(mPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %q: %v\n", mPath, err)
|
||||
}
|
||||
f, err := modfile.Parse(mPath, content, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &protoModule{pathToGoMod: path, mf: f}, nil
|
||||
}
|
||||
|
||||
func getPathsToModules(
|
||||
repoRoot string, exclusions []string) (result []string, err error) {
|
||||
exclusionMap := utils.SliceToSet(exclusions)
|
||||
err = filepath.Walk(
|
||||
repoRoot,
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("trouble at pathToGoMod %q: %v\n", path, err)
|
||||
}
|
||||
if info.IsDir() {
|
||||
if _, ok := exclusionMap[info.Name()]; ok {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if info.Name() == goModFile {
|
||||
result = append(result, path[:len(path)-len(goModFile)-1])
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
47
cmd/gorepomod/internal/repo/protomodule_test.go
Normal file
47
cmd/gorepomod/internal/repo/protomodule_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/mod/modfile"
|
||||
"golang.org/x/mod/module"
|
||||
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/misc"
|
||||
)
|
||||
|
||||
func TestShortName(t *testing.T) {
|
||||
var testCases = map[string]struct {
|
||||
name misc.ModuleShortName
|
||||
modFile *modfile.File
|
||||
}{
|
||||
"one": {
|
||||
name: misc.ModuleShortName("garage"),
|
||||
modFile: &modfile.File{
|
||||
Module: &modfile.Module{
|
||||
Mod: module.Version{
|
||||
Path: "gh.com/micheal/garage",
|
||||
Version: "v2.3.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"three": {
|
||||
name: misc.ModuleShortName("fruit/yellow/banana"),
|
||||
modFile: &modfile.File{
|
||||
Module: &modfile.Module{
|
||||
Mod: module.Version{
|
||||
Path: "gh.com/micheal/fruit/yellow/banana",
|
||||
Version: "v2.3.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for n, tc := range testCases {
|
||||
m := protoModule{pathToGoMod: "irrelevant", mf: tc.modFile}
|
||||
actual := m.ShortName("gh.com/micheal")
|
||||
if actual != tc.name {
|
||||
t.Errorf(
|
||||
"%s: expected %s, got %s", n, tc.name, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user