Skip to content

Commit

Permalink
feat(check-plugin): apply fixes flag
Browse files Browse the repository at this point in the history
Add --fix / -x which automatically applies go.mod fixes instead of
just printing them out.
  • Loading branch information
stevenh committed Aug 28, 2024
1 parent a7448e2 commit d726e8d
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 65 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0
github.com/spf13/cobra v1.1.3
github.com/stretchr/testify v1.9.0
golang.org/x/mod v0.12.0
golang.org/x/mod v0.19.0
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -605,8 +605,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down
148 changes: 122 additions & 26 deletions plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,33 @@ import (
"github.com/krakendio/krakend-cobra/v2/plugin"
"github.com/spf13/cobra"
"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
)

// indirectRequires returns the indirect dependencies of the go.sum file.
func indirectRequires(goSum string) (map[string]struct{}, error) {
dir := filepath.Dir(goSum)
filename := filepath.Join(dir, "go.mod")
const (
// goName is the name of the diff which represents the go version.
goName = "go"

// libcName is the name of the diff which represents the libc version.
libcName = "libc"
)

// goMod returns the go.mod file path from the go.sum file path.
func goMod(goSum string) string {
return filepath.Join(filepath.Dir(goSum), "go.mod")
}

// indirectRequires returns the details and indirect dependencies of the go.sum file.
func indirectRequires(goSum string) (*modfile.File, map[string]struct{}, error) {
filename := goMod(goSum)
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("reading go.mod: %w", err)
return nil, nil, fmt.Errorf("read go.mod: %w", err)
}

f, err := modfile.Parse(filename, data, nil)
if err != nil {
return nil, fmt.Errorf("parsing go.mod: %w", err)
return nil, nil, fmt.Errorf("parse go.mod: %w", err)
}

indirects := map[string]struct{}{}
Expand All @@ -31,7 +44,23 @@ func indirectRequires(goSum string) (map[string]struct{}, error) {
}
}

return indirects, nil
return f, indirects, nil
}

// writeModFile writes the modfile.File to the go.mod file determined from goSum.
func writeModFile(goSum string, f *modfile.File) error {
f.Cleanup()
data, err := f.Format()
if err != nil {
return fmt.Errorf("format go.mod: %w", err)
}

filename := goMod(goSum)
if err = os.WriteFile(filename, data, 0644); err != nil {
return fmt.Errorf("write go.sum: %w", err)
}

return nil
}

// getBuildInfo returns the dependencies of the binary calling it.
Expand Down Expand Up @@ -66,32 +95,99 @@ func pluginFuncErr(cmd *cobra.Command, _ []string) error {
return nil
}

if gogetEnabled {
indirects, err := indirectRequires(goSum)
if err != nil {
var indirects map[string]struct{}
var modFile *modfile.File
if gogetEnabled || fixEnabled {
if modFile, indirects, err = indirectRequires(goSum); err != nil {
return err
}
for _, diff := range diffs {
if diff.Name != "go" && diff.Name != "libc" {
if _, ok := indirects[diff.Name]; ok {
cmd.Printf("go mod edit --replace %s=%s@%s\n", diff.Name, diff.Name, diff.Expected)
} else {
cmd.Printf("go get %s@%s\n", diff.Name, diff.Expected)
}
}

var fixed int
if !fixEnabled {
outputFixes(cmd, diffs, indirects)
} else if fixed, err = applyFixes(cmd, diffs, modFile, indirects); err != nil {
return err
}

// Report any remaining incompatibilities.
if len(diffs) != fixed {
if fixed > 0 {
return fmt.Errorf("%d incompatibilities fixed, %d left", fixed, len(diffs)-fixed)
}

return fmt.Errorf("%d incompatibilities found", len(diffs))
}

return nil
}

// outputFixes prints the incompatibilities.
func outputFixes(cmd *cobra.Command, diffs []plugin.Diff, indirects map[string]struct{}) {
for _, diff := range diffs {
if diff.Name != goName && diff.Name != libcName && gogetEnabled {
if _, ok := indirects[diff.Name]; ok {
cmd.Printf("go mod edit --replace %s=%s@%s\n", diff.Name, diff.Name, diff.Expected)
} else {
cmd.Printf("go get %s@%s\n", diff.Name, diff.Expected)
}
continue
}

cmd.Println(diff.Name)
cmd.Println("\thave:", diff.Have)
cmd.Println("\twant:", diff.Expected)
}
}

// applyFixes applies the fixes and returns the number of incompatibilities fixed.
func applyFixes(cmd *cobra.Command, diffs []plugin.Diff, modFile *modfile.File, indirects map[string]struct{}) (int, error) {
var replaces []plugin.Diff
var requires []*modfile.Require
for _, diff := range diffs {
if diff.Name != goName && diff.Name != libcName {
if _, ok := indirects[diff.Name]; ok {
replaces = append(replaces, diff)
continue
}

cmd.Println(diff.Name)
cmd.Println("\thave:", diff.Have)
cmd.Println("\twant:", diff.Expected)
requires = append(requires, &modfile.Require{
Mod: module.Version{
Path: diff.Name,
Version: diff.Expected,
},
})
continue
}
} else {
for _, diff := range diffs {
cmd.Println(diff.Name)
cmd.Println("\thave:", diff.Have)
cmd.Println("\twant:", diff.Expected)

cmd.Println(diff.Name)
cmd.Println("\thave:", diff.Have)
cmd.Println("\twant:", diff.Expected)
}

if len(requires) > 0 {
// We use the modfile.SetRequireSeparateIndirect to avoid adding direct
// dependencies to the direct block as per:
// https://github.com/golang/go/issues/69050
modFile.SetRequireSeparateIndirect(append(modFile.Require, requires...))
}

// Add replaces after requires.
for _, diff := range replaces {
if err := modFile.AddReplace(diff.Name, "", diff.Name, diff.Expected); err != nil {
return 0, fmt.Errorf("add replace: %w", err)
}
}

return fmt.Errorf("%d incompatibilities found", len(diffs))
fixed := len(replaces) + len(requires)
if fixed > 0 {
modFile.Cleanup()
if err := writeModFile(goSum, modFile); err != nil {
return 0, err
}

cmd.Printf("%d incompatibilities fixed\n", fixed)
}

return fixed, nil
}
Loading

0 comments on commit d726e8d

Please sign in to comment.