2022-07-22 14:09:34 +00:00
|
|
|
package cli
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2022-07-22 14:26:07 +00:00
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2022-07-22 14:09:34 +00:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/urfave/cli/v2"
|
2023-02-25 16:14:43 +00:00
|
|
|
|
|
|
|
"github.com/make-go-great/color-go"
|
2022-07-22 14:09:34 +00:00
|
|
|
)
|
|
|
|
|
2022-07-22 15:06:29 +00:00
|
|
|
const (
|
2022-07-22 15:40:39 +00:00
|
|
|
gitDirectory = ".git"
|
|
|
|
vendorDirectory = "vendor"
|
2022-08-24 15:06:05 +00:00
|
|
|
|
|
|
|
goModFile = "go.mod"
|
|
|
|
goSumFile = "go.sum"
|
2022-08-24 14:51:54 +00:00
|
|
|
|
|
|
|
defaultCountModule = 100
|
2022-08-24 15:01:09 +00:00
|
|
|
depsFileComment = "#"
|
2022-07-22 15:06:29 +00:00
|
|
|
)
|
|
|
|
|
2022-07-22 14:26:07 +00:00
|
|
|
var (
|
|
|
|
ErrInvalidModuleVersion = errors.New("invalid module version")
|
|
|
|
ErrFailedStatusCode = errors.New("failed status code")
|
|
|
|
)
|
2022-07-22 14:09:34 +00:00
|
|
|
|
|
|
|
// See https://pkg.go.dev/cmd/go#hdr-List_packages_or_modules
|
|
|
|
type Module struct {
|
2022-09-21 17:39:57 +00:00
|
|
|
Update *Module
|
|
|
|
Replace *Module
|
|
|
|
Path string
|
|
|
|
Version string
|
|
|
|
Main bool
|
|
|
|
Indirect bool
|
2022-07-22 14:09:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a *action) Run(c *cli.Context) error {
|
|
|
|
a.getFlags(c)
|
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
mapImportedModules, err := a.runGetImportedModules(c)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check vendor exist to run go mod vendor
|
|
|
|
existVendor := false
|
|
|
|
if _, err := os.Stat(vendorDirectory); err == nil {
|
|
|
|
existVendor = true
|
|
|
|
}
|
|
|
|
|
2023-02-25 16:05:32 +00:00
|
|
|
depsStr, useDepFile, err := a.runReadDepsFile(c)
|
2022-07-22 15:40:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-07-22 15:50:51 +00:00
|
|
|
if depsStr == "" {
|
2022-07-22 15:40:39 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read deps file line by line to upgrade
|
2022-08-24 14:51:54 +00:00
|
|
|
successUpgradedModules := make([]Module, 0, defaultCountModule)
|
2022-07-22 15:50:51 +00:00
|
|
|
modulePaths := strings.Split(depsStr, "\n")
|
2022-07-22 15:40:39 +00:00
|
|
|
for _, modulePath := range modulePaths {
|
2022-08-24 15:11:28 +00:00
|
|
|
successUpgradedModules, err = a.runUpgradeModule(
|
|
|
|
c,
|
|
|
|
mapImportedModules,
|
|
|
|
successUpgradedModules,
|
|
|
|
modulePath,
|
|
|
|
)
|
2022-07-22 15:40:39 +00:00
|
|
|
if err != nil {
|
|
|
|
color.PrintAppError(name, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := a.runGoMod(c, existVendor); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-02-25 16:05:32 +00:00
|
|
|
if err := a.runGitCommit(c, successUpgradedModules, existVendor, useDepFile); err != nil {
|
2022-07-22 15:40:39 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-08-28 17:09:20 +00:00
|
|
|
// Get all imported modules
|
|
|
|
func (a *action) runGetImportedModules(c *cli.Context) (map[string]Module, error) {
|
2022-07-22 14:09:34 +00:00
|
|
|
goListAllArgs := []string{"list", "-m", "-json", "all"}
|
2022-09-21 17:39:57 +00:00
|
|
|
goOutput, err := exec.CommandContext(c.Context, "go", goListAllArgs...).CombinedOutput()
|
2022-07-22 14:09:34 +00:00
|
|
|
if err != nil {
|
2022-08-28 17:09:20 +00:00
|
|
|
return nil, fmt.Errorf("failed to run go %s: %w", strings.Join(goListAllArgs, " "), err)
|
2022-07-22 14:09:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// goAllOutput is like {...}\n{...}\n{...}
|
|
|
|
// Missing [] and , for json
|
|
|
|
goOutputStr := strings.ReplaceAll(strings.TrimSpace(string(goOutput)), "\n", "")
|
|
|
|
goOutputStr = strings.ReplaceAll(goOutputStr, "}{", "},{")
|
|
|
|
goOutputStr = "[" + goOutputStr + "]"
|
|
|
|
|
2022-08-24 14:51:54 +00:00
|
|
|
importedModules := make([]Module, 0, defaultCountModule)
|
2022-07-22 14:09:34 +00:00
|
|
|
if err := json.Unmarshal([]byte(goOutputStr), &importedModules); err != nil {
|
2022-07-22 15:40:39 +00:00
|
|
|
return nil, fmt.Errorf("failed to json unmarshal: %w", err)
|
2022-07-22 14:09:34 +00:00
|
|
|
}
|
2022-08-24 15:38:51 +00:00
|
|
|
a.log("Go output: %s\n", string(goOutput))
|
2022-07-22 14:09:34 +00:00
|
|
|
|
2022-08-24 15:23:57 +00:00
|
|
|
mapImportedModules := make(map[string]Module)
|
2022-07-22 14:09:34 +00:00
|
|
|
for _, importedModule := range importedModules {
|
2022-09-21 17:39:57 +00:00
|
|
|
// Ignore main module
|
|
|
|
if importedModule.Main {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ignore indirect module
|
|
|
|
if importedModule.Indirect {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-08-24 15:23:57 +00:00
|
|
|
// Ignore replace module
|
|
|
|
if importedModule.Replace != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
mapImportedModules[importedModule.Path] = importedModule
|
2022-07-22 14:09:34 +00:00
|
|
|
}
|
2022-08-28 17:09:20 +00:00
|
|
|
|
2022-08-16 03:34:55 +00:00
|
|
|
a.log("Imported modules: %+v\n", importedModules)
|
2022-07-22 14:09:34 +00:00
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
return mapImportedModules, nil
|
|
|
|
}
|
|
|
|
|
2023-02-25 16:11:10 +00:00
|
|
|
func (a *action) runReadDepsFile(c *cli.Context) (depsStr string, useDepFile bool, err error) {
|
2022-07-22 15:50:51 +00:00
|
|
|
// Try to read from url first
|
|
|
|
if a.flags.depsURL != "" {
|
|
|
|
depsURL, err := url.Parse(a.flags.depsURL)
|
2022-07-22 14:26:07 +00:00
|
|
|
if err != nil {
|
2023-02-25 16:05:32 +00:00
|
|
|
return "", false, fmt.Errorf("failed to parse deps file url %s: %w", a.flags.depsURL, err)
|
2022-07-22 15:50:51 +00:00
|
|
|
}
|
|
|
|
|
2023-06-29 03:46:34 +00:00
|
|
|
// nolint:noctx
|
2022-07-22 15:50:51 +00:00
|
|
|
httpRsp, err := http.Get(depsURL.String())
|
|
|
|
if err != nil {
|
2023-02-25 16:05:32 +00:00
|
|
|
return "", false, fmt.Errorf("failed to http get %s: %w", depsURL.String(), err)
|
2022-07-22 14:26:07 +00:00
|
|
|
}
|
|
|
|
defer httpRsp.Body.Close()
|
|
|
|
|
|
|
|
if httpRsp.StatusCode != http.StatusOK {
|
2023-02-25 16:05:32 +00:00
|
|
|
return "", false, fmt.Errorf("http status code not ok %d: %w", httpRsp.StatusCode, ErrFailedStatusCode)
|
2022-07-22 14:26:07 +00:00
|
|
|
}
|
|
|
|
|
2022-07-22 15:50:51 +00:00
|
|
|
depsBytes, err := io.ReadAll(httpRsp.Body)
|
2022-07-22 14:26:07 +00:00
|
|
|
if err != nil {
|
2023-02-25 16:05:32 +00:00
|
|
|
return "", false, fmt.Errorf("failed to read http response body: %w", err)
|
2022-07-22 14:26:07 +00:00
|
|
|
}
|
|
|
|
|
2023-02-25 16:05:32 +00:00
|
|
|
return strings.TrimSpace(string(depsBytes)), false, nil
|
2022-07-22 15:50:51 +00:00
|
|
|
}
|
2022-07-22 14:26:07 +00:00
|
|
|
|
2022-07-22 15:50:51 +00:00
|
|
|
// If empty url, try to read from file
|
|
|
|
depsBytes, err := os.ReadFile(a.flags.depsFile)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
color.PrintAppWarning(name, fmt.Sprintf("deps file [%s] not found", a.flags.depsFile))
|
2023-02-25 16:05:32 +00:00
|
|
|
return "", false, nil
|
2022-07-22 14:09:34 +00:00
|
|
|
}
|
|
|
|
|
2023-02-25 16:05:32 +00:00
|
|
|
return "", false, fmt.Errorf("failed to read file %s: %w", a.flags.depsFile, err)
|
2022-07-22 14:09:34 +00:00
|
|
|
}
|
|
|
|
|
2023-02-25 16:05:32 +00:00
|
|
|
return strings.TrimSpace(string(depsBytes)), true, nil
|
2022-07-22 15:40:39 +00:00
|
|
|
}
|
2022-07-22 15:06:29 +00:00
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
func (a *action) runUpgradeModule(
|
|
|
|
c *cli.Context,
|
2022-08-24 15:23:57 +00:00
|
|
|
mapImportedModules map[string]Module,
|
2022-07-22 15:40:39 +00:00
|
|
|
successUpgradedModules []Module,
|
|
|
|
modulePath string,
|
|
|
|
) ([]Module, error) {
|
|
|
|
modulePath = strings.TrimSpace(modulePath)
|
2022-08-24 15:01:09 +00:00
|
|
|
|
|
|
|
// Ignore empty
|
2022-07-22 15:40:39 +00:00
|
|
|
if modulePath == "" {
|
|
|
|
return successUpgradedModules, nil
|
|
|
|
}
|
2022-08-24 15:01:09 +00:00
|
|
|
|
|
|
|
// Ignore comment
|
|
|
|
if strings.HasPrefix(modulePath, depsFileComment) {
|
|
|
|
return successUpgradedModules, nil
|
|
|
|
}
|
|
|
|
|
2022-08-24 15:38:51 +00:00
|
|
|
a.log("Module path: %s\n", modulePath)
|
2022-07-22 14:09:34 +00:00
|
|
|
|
2022-08-24 15:23:57 +00:00
|
|
|
// Ignore not imported module
|
2022-07-22 15:40:39 +00:00
|
|
|
if _, ok := mapImportedModules[modulePath]; !ok {
|
2022-08-24 15:38:51 +00:00
|
|
|
a.log("%s is not imported module\n", modulePath)
|
2022-07-22 15:40:39 +00:00
|
|
|
return successUpgradedModules, nil
|
|
|
|
}
|
2022-07-22 14:09:34 +00:00
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
// Get module latest version
|
|
|
|
goListArgs := []string{"list", "-m", "-u", "-json", modulePath}
|
|
|
|
goOutput, err := exec.CommandContext(c.Context, "go", goListArgs...).CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return successUpgradedModules, fmt.Errorf("failed to run go %+v: %w", strings.Join(goListArgs, " "), err)
|
|
|
|
}
|
2022-08-24 15:38:51 +00:00
|
|
|
a.log("Go output: %s\n", string(goOutput))
|
2022-07-22 14:09:34 +00:00
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
module := Module{}
|
|
|
|
if err := json.Unmarshal(goOutput, &module); err != nil {
|
|
|
|
return successUpgradedModules, fmt.Errorf("failed to json unmarshal: %w", err)
|
|
|
|
}
|
2022-08-24 15:38:51 +00:00
|
|
|
a.log("Module: %+v\n", module)
|
2022-07-22 14:09:34 +00:00
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
if module.Update == nil {
|
|
|
|
color.PrintAppOK(name, fmt.Sprintf("You already have latest [%s] version [%s]", module.Path, module.Version))
|
|
|
|
return successUpgradedModules, nil
|
|
|
|
}
|
2022-07-22 14:09:34 +00:00
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
// Upgrade module
|
|
|
|
if a.flags.dryRun {
|
2022-08-24 15:23:57 +00:00
|
|
|
// Only print which module will be upgraded
|
2022-08-24 15:03:09 +00:00
|
|
|
// Don't do anything
|
2022-07-22 15:40:39 +00:00
|
|
|
color.PrintAppOK(name, fmt.Sprintf("Will upgrade [%s] version [%s] to [%s]", module.Path, module.Version, module.Update.Version))
|
|
|
|
return successUpgradedModules, nil
|
|
|
|
}
|
2022-07-22 14:09:34 +00:00
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
goGetArgs := []string{"get", "-d", modulePath + "@" + module.Update.Version}
|
|
|
|
goOutput, err = exec.CommandContext(c.Context, "go", goGetArgs...).CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return successUpgradedModules, fmt.Errorf("failed to run go %+v: %w", strings.Join(goGetArgs, " "), err)
|
|
|
|
}
|
2022-08-24 15:38:51 +00:00
|
|
|
a.log("Go output: %s\n", string(goOutput))
|
2022-07-22 14:09:34 +00:00
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
successUpgradedModules = append(successUpgradedModules, module)
|
2022-07-22 15:06:29 +00:00
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
color.PrintAppOK(name, fmt.Sprintf("Upgraded [%s] version [%s] to [%s] success", module.Path, module.Version, module.Update.Version))
|
|
|
|
|
|
|
|
return successUpgradedModules, nil
|
|
|
|
}
|
2022-07-22 14:09:34 +00:00
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
func (a *action) runGoMod(c *cli.Context, existVendor bool) error {
|
2022-07-22 15:42:34 +00:00
|
|
|
if a.flags.dryRun {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-07-22 15:06:29 +00:00
|
|
|
// go mod tidy
|
2022-07-22 14:09:34 +00:00
|
|
|
goModArgs := []string{"mod", "tidy"}
|
2022-07-22 15:40:39 +00:00
|
|
|
goOutput, err := exec.CommandContext(c.Context, "go", goModArgs...).CombinedOutput()
|
2022-07-22 14:09:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to run go %+v: %w", strings.Join(goModArgs, " "), err)
|
|
|
|
}
|
2022-08-24 15:38:51 +00:00
|
|
|
a.log("Go output: %s\n", string(goOutput))
|
2022-07-22 14:09:34 +00:00
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
if existVendor {
|
|
|
|
// go mod vendor
|
|
|
|
goModArgs = []string{"mod", "vendor"}
|
|
|
|
goOutput, err = exec.CommandContext(c.Context, "go", goModArgs...).CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to run go %+v: %w", strings.Join(goModArgs, " "), err)
|
|
|
|
}
|
2022-08-24 15:38:51 +00:00
|
|
|
a.log("Go output: %s\n", string(goOutput))
|
2022-07-22 15:40:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-02-25 16:05:32 +00:00
|
|
|
func (a *action) runGitCommit(c *cli.Context, successUpgradedModules []Module, existVendor, useDepFile bool) error {
|
2022-07-22 15:40:39 +00:00
|
|
|
if a.flags.dryRun {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not exist git, stop
|
2022-07-22 15:06:29 +00:00
|
|
|
if _, err := os.Stat(gitDirectory); err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("failed to stat %s: %w", gitDirectory, err)
|
|
|
|
}
|
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
// If there is no upgrade module, stop
|
|
|
|
if len(successUpgradedModules) == 0 {
|
2022-07-22 15:10:38 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
// git add
|
|
|
|
gitAddArgs := []string{"add", goModFile, goSumFile}
|
|
|
|
if existVendor {
|
|
|
|
gitAddArgs = append(gitAddArgs, vendorDirectory)
|
|
|
|
}
|
2023-02-25 16:05:32 +00:00
|
|
|
if useDepFile {
|
|
|
|
gitAddArgs = append(gitAddArgs, a.flags.depsFile)
|
|
|
|
}
|
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
gitOutput, err := exec.CommandContext(c.Context, "git", gitAddArgs...).CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to run git %+v: %w", strings.Join(gitAddArgs, " "), err)
|
2022-07-22 15:10:38 +00:00
|
|
|
}
|
2022-08-24 15:38:51 +00:00
|
|
|
a.log("Git output: %s\n", string(gitOutput))
|
2022-07-22 15:10:38 +00:00
|
|
|
|
2022-07-22 15:40:39 +00:00
|
|
|
// git commit
|
2022-07-22 15:06:29 +00:00
|
|
|
gitCommitMessage := "build: upgrade modules\n"
|
2022-07-22 15:40:39 +00:00
|
|
|
for _, module := range successUpgradedModules {
|
2022-07-22 15:06:29 +00:00
|
|
|
gitCommitMessage += fmt.Sprintf("\n%s: %s -> %s", module.Path, module.Version, module.Update.Version)
|
|
|
|
}
|
2022-07-22 15:52:53 +00:00
|
|
|
gitCommitArgs := []string{"commit", "-m", gitCommitMessage}
|
2022-07-22 15:40:39 +00:00
|
|
|
gitOutput, err = exec.CommandContext(c.Context, "git", gitCommitArgs...).CombinedOutput()
|
2022-07-22 15:06:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to run git %+v: %w", strings.Join(gitCommitArgs, " "), err)
|
|
|
|
}
|
2022-08-24 15:38:51 +00:00
|
|
|
a.log("Git output: %s\n", string(gitOutput))
|
2022-07-22 15:06:29 +00:00
|
|
|
|
2022-07-22 14:09:34 +00:00
|
|
|
return nil
|
|
|
|
}
|