Skip to content

Commit

Permalink
Add update-api-module-path go program and make target.
Browse files Browse the repository at this point in the history
  • Loading branch information
Joerger committed Oct 6, 2021
1 parent 228d37d commit 7a89d66
Show file tree
Hide file tree
Showing 8 changed files with 2,682 additions and 1 deletion.
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,22 @@ version: $(VERSRC)
# This rule triggers re-generation of version files specified if Makefile changes.
$(VERSRC): Makefile
VERSION=$(VERSION) $(MAKE) -f version.mk setver
# Update api module path, but don't fail on error.
$(MAKE) update-api-module-path || true

# This rule updates the api module path to be in sync with the current api release version.
# e.g. github.com/gravitational/teleport/api/vX -> github.com/gravitational/teleport/api/vY
#
# It will immediately fail if:
# 1. A suffix is present in the version - e.g. "-alpha"
# 2. The version suffix hasn't changed - e.g. 7.0.0 -> 7.1.0
#
# Note: any build flags needed to compile go files (such as build tags) should be provided below.
.PHONY: update-api-module-path
update-api-module-path:
go run build.assets/update-api-module-path/main.go -tags "bpf fips pam roletester desktop_access_beta"
$(MAKE) update-vendor
$(MAKE) grpc

# make tag - prints a tag to use with git for the current version
# To put a new release on Github:
Expand Down
273 changes: 273 additions & 0 deletions build.assets/update-api-module-path/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
/*
Copyright 2021 Gravitational, Inc.
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 (
"go/format"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/gravitational/teleport/api"
"github.com/gravitational/teleport/lib/utils"

"github.com/gravitational/trace"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"golang.org/x/mod/modfile"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/packages"
)

func init() {
utils.InitLogger(utils.LoggingForCLI, log.DebugLevel)
}

func main() {
// the api mod path should only be updated on releases, check for non-release suffixes
if strings.Contains(api.Version, "-") {
exitWithMessage("the current api version is not a release, continue without updating")
}

// get old api mod path from `api/go.mod`
oldModPath, err := getModImportPath("./api")
if err != nil {
exitWithError(trace.Wrap(err, "failed to get mod path"))
}

// get new api mod path using version in `api/version.go`
newModPath := getNewPath(oldModPath)
if newModPath == oldModPath {
exitWithMessage("the api module path has not changed, continue without updating")
}

// update go files within the teleport/api and teleport modules
log.Info("Updating teleport/api module...")
if err := updateGoModule("./api", oldModPath, newModPath); err != nil {
exitWithError(trace.Wrap(err, "failed to update teleport/api module"))
}
log.Info("Updating teleport module...")
if err := updateGoModule("./", oldModPath, newModPath); err != nil {
exitWithError(trace.Wrap(err, "failed to update teleport module"))
}

// Update .proto files in teleport/api with new mod path
log.Info("Updating .proto files...")
if err := updateFiles("./api", ".proto", oldModPath, newModPath); err != nil {
exitWithError(trace.Wrap(err, "failed to update teleport mod file"))
}
}

// updateGoModule updates instances of the oldModPath with the newModPath in the given go module.
func updateGoModule(modulePath, oldModPath, newModPath string) error {
var buildFlags []string
if len(os.Args) > 1 {
buildFlags = os.Args[1:]
log.Infof(" Using buildFlags: %v", buildFlags)
} else {
log.Info(" Updating without build flags. This will exclude some packages, such as /teleport/lib/bpf.")
}

mode := packages.NeedTypes | packages.NeedSyntax
cfg := &packages.Config{Mode: mode, Tests: true, Dir: modulePath, BuildFlags: buildFlags}
pkgs, err := packages.Load(cfg, "./...")
if err != nil {
return trace.Wrap(err)
}

log.Info(" Updating go files...")
var errs []error
packages.Visit(pkgs, func(pkg *packages.Package) bool {
if err = updateGoImports(pkg, oldModPath, newModPath); err != nil {
errs = append(errs, err)
return false
}
return true
}, nil)

if len(errs) != 0 {
return trace.NewAggregate(errs...)
}

log.Info(" Updating go.mod...")
if err := updateModFile(modulePath, oldModPath, newModPath); err != nil {
return trace.Wrap(err, "failed to update mod file for module")
}

return nil
}

// updateGoImports updates instances of the oldModPath with the newModPath in the given package.
func updateGoImports(p *packages.Package, oldModPath, newModPath string) error {
for _, syn := range p.Syntax {
var rewritten bool
for _, i := range syn.Imports {
imp := strings.Replace(i.Path.Value, "\"", "", 2)
if strings.HasPrefix(imp, oldModPath) && !strings.HasPrefix(imp, newModPath) {
newImp := strings.Replace(imp, oldModPath, newModPath, 1)
if astutil.RewriteImport(p.Fset, syn, imp, newImp) {
rewritten = true
}
}
}
if !rewritten {
continue
}

goFileName := p.Fset.File(syn.Pos()).Name()
f, err := os.Create(goFileName)
if err != nil {
return errors.Wrapf(err, "could not create go file %v", goFileName)
}
defer f.Close()

if err = format.Node(f, p.Fset, syn); err != nil {
return errors.Wrapf(err, "could not rewrite go file %v", goFileName)
}
}

return nil
}

// updateModFile updates instances of oldPath to newPath in a go.mod file.
// The modFile is updated in place by updating the syntax fields directly.
func updateModFile(dir, oldPath, newPath string) error {
modFile, err := getModFile(dir)
if err != nil {
return trace.Wrap(err)
}
// Update mod name if needed
if modFile.Module.Syntax.Token[1] == oldPath {
modFile.Module.Syntax.Token[1] = newPath
}
// Update require statements in place if needed
for _, r := range modFile.Require {
if r.Mod.Path == oldPath {
pathI, versionI := 0, 1
if r.Syntax.Token[0] == "require" {
pathI, versionI = 1, 2
}
r.Syntax.Token[pathI], r.Syntax.Token[versionI] = newPath, currentVersionString()
}
}
// Update replace statements in place if needed
for _, r := range modFile.Replace {
if r.Old.Path == oldPath {
pathI := 0
if r.Syntax.Token[0] == "replace" {
pathI = 1
}
r.Syntax.Token[pathI] = newPath
}
}
// Format and save mod file
bts, err := modFile.Format()
if err != nil {
return errors.Wrap(err, "could not format go.mod file with new import path")
}
if err = ioutil.WriteFile(filepath.Join(dir, "go.mod"), bts, 0660); err != nil {
return errors.Wrap(err, "could not rewrite go.mod file")
}
return nil
}

// updateFiles updates instances of the oldModPath with the newModPath
// in files with the given fileExtension in the given directory.
func updateFiles(dir, fileExtension, oldModPath, newModPath string) error {
return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(d.Name(), fileExtension) {
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
fileString := strings.ReplaceAll(string(data), oldModPath, newModPath)
if err := ioutil.WriteFile(path, []byte(fileString), 0660); err != nil {
return err
}
}
return nil
})
}

// getModImportPath gets the module's currently set path/name
func getModImportPath(dir string) (string, error) {
modFile, err := getModFile(dir)
if err != nil {
return "", trace.Wrap(err)
}
if modFile.Module.Mod.Path == "" {
return "", trace.Errorf("could not get mod path")
}
return modFile.Module.Mod.Path, nil
}

// getModFile returns an AST of the given go.mod file
func getModFile(dir string) (*modfile.File, error) {
modPath := filepath.Join(dir, "go.mod")
bts, err := ioutil.ReadFile(modPath)
if err != nil {
return nil, trace.Wrap(err)
}
f, err := modfile.Parse(modPath, bts, nil)
if err != nil {
return nil, trace.Wrap(err)
}
return f, nil
}

// getNewPath gets the new module path for the current version
func getNewPath(path string) string {
return trimVersionSuffix(path) + currentMajorVersionSuffix()
}

// trimVersionSuffix trims the version suffix "/vX" off of the end of an import path.
func trimVersionSuffix(path string) string {
splitPath := strings.Split(path, "/")
last := splitPath[len(splitPath)-1]
if strings.HasPrefix(last, "v") {
return strings.Join(splitPath[:len(splitPath)-1], "/")
}
return path
}

// currentVersionString returns the current api version in the format "vX.Y.Z"
func currentVersionString() string {
return "v" + api.Version
}

// currentVersionSuffix returns the current api version suffix
// e.g "" for v0/v1 or "/vX" for vX where X >= 2
func currentMajorVersionSuffix() string {
if majVer := strings.Split(currentVersionString(), ".")[0]; majVer >= "v2" {
return "/" + majVer
}
return ""
}

func exitWithError(err error) {
if err != nil {
log.WithError(err).Error()
}
os.Exit(1)
}

func exitWithMessage(message string) {
log.Info(message)
os.Exit(1)
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,13 @@ require (
go.uber.org/atomic v1.7.0
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
golang.org/x/mod v0.4.2
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d
golang.org/x/text v0.3.5
golang.org/x/tools v0.1.5 // indirect
golang.org/x/tools v0.1.5
google.golang.org/api v0.29.0
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210223151946-22b48be4551b
Expand Down
78 changes: 78 additions & 0 deletions vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 7a89d66

Please sign in to comment.