diff --git a/go.mod b/go.mod index 7d15d5d..419350a 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/mattn/go-isatty v0.0.19 github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 github.com/spf13/cobra v0.0.5 + github.com/stretchr/testify v1.9.0 golang.org/x/mod v0.8.0 ) @@ -51,6 +52,7 @@ require ( github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/clbanning/mxj v1.8.4 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect @@ -123,6 +125,7 @@ require ( github.com/pelletier/go-toml v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20180406234716-d932a24a8ccb // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sony/gobreaker v0.4.1 // indirect diff --git a/go.sum b/go.sum index f51d59f..970598f 100644 --- a/go.sum +++ b/go.sum @@ -772,8 +772,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -784,8 +784,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= diff --git a/plugin.go b/plugin.go index e482ab0..16c6b5a 100644 --- a/plugin.go +++ b/plugin.go @@ -1,40 +1,76 @@ package cmd import ( + "fmt" "os" + "path/filepath" "github.com/krakendio/krakend-cobra/v2/plugin" "github.com/spf13/cobra" + "golang.org/x/mod/modfile" ) -func pluginFunc(cmd *cobra.Command, _ []string) { +// 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") + data, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("read go.mod: %w", err) + } + + f, err := modfile.Parse(filename, data, nil) + if err != nil { + return nil, fmt.Errorf("parse go.mod: %w", err) + } + + indirects := map[string]struct{}{} + for _, r := range f.Require { + if r.Indirect { + indirects[r.Mod.Path] = struct{}{} + } + } + + return indirects, nil +} + +// getBuildInfo returns the dependencies of the binary calling it. +// It is a var to allow the replacement of the function in the tests +// as the debug.ReadBuildInfo function is not available in the tests +// https://github.com/golang/go/issues/68045 +var localDescriber = plugin.Local + +func pluginFunc(cmd *cobra.Command, _ []string) error { f, err := os.Open(goSum) if err != nil { - cmd.Println(err) - os.Exit(1) - return + return err } + defer f.Close() //nolint:errcheck // Read only file so We would have returned an error before if this failed. + desc, err := plugin.Describe(f, goVersion, libcVersion) if err != nil { - cmd.Println(err) - f.Close() - os.Exit(1) - return + return err } - diffs := plugin.Local().Compare(desc) + diffs := localDescriber().Compare(desc) if len(diffs) == 0 { cmd.Println("No incompatibilities found!") - f.Close() - return + return nil } - cmd.Println(len(diffs), "incompatibility(ies) found...") if gogetEnabled { + indirects, err := indirectRequires(goSum) + if err != nil { + return err + } for _, diff := range diffs { if diff.Name != "go" && diff.Name != "libc" { - cmd.Printf("go get %s@%s\n", diff.Name, diff.Expected) + 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 } @@ -42,15 +78,13 @@ func pluginFunc(cmd *cobra.Command, _ []string) { cmd.Println("\thave:", diff.Have) cmd.Println("\twant:", diff.Expected) } - f.Close() - os.Exit(1) + } else { + for _, diff := range diffs { + cmd.Println(diff.Name) + cmd.Println("\thave:", diff.Have) + cmd.Println("\twant:", diff.Expected) + } } - for _, diff := range diffs { - cmd.Println(diff.Name) - cmd.Println("\thave:", diff.Have) - cmd.Println("\twant:", diff.Expected) - } - f.Close() - os.Exit(1) + return fmt.Errorf("%d incompatibilities found", len(diffs)) } diff --git a/plugin_test.go b/plugin_test.go new file mode 100644 index 0000000..9a8de01 --- /dev/null +++ b/plugin_test.go @@ -0,0 +1,90 @@ +package cmd + +import ( + "bytes" + "testing" + + "github.com/krakendio/krakend-cobra/v2/plugin" + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" +) + +func Test_pluginFunc(t *testing.T) { + var buf bytes.Buffer + cmd := &cobra.Command{} + cmd.SetOutput(&buf) + + localDescriber = func() plugin.Descriptor { + return plugin.Descriptor{ + Go: goVersion, + Libc: libcVersion, + Deps: map[string]string{ + "golang.org/x/mod": "v0.6.0-dev.0.20220419223038-86c51ed26bb4", + "github.com/Azure/azure-sdk-for-go": "v59.3.0+incompatible", + "cloud.google.com/go": "v0.100.2", + }, + } + } + + defer func() { localDescriber = plugin.Local }() + + tests := map[string]struct { + goSum string + expected string + fix bool + err string + }{ + "missing": { + goSum: "./testdata/missing-go.sum", + err: "open ./testdata/missing-go.sum: no such file or directory", + }, + "matching": { + goSum: "./testdata/match-go.sum", + expected: "No incompatibilities found!\n", + }, + "changes": { + goSum: "./testdata/changes-go.sum", + expected: `cloud.google.com/go + have: v0.100.3 + want: v0.100.2 +github.com/Azure/azure-sdk-for-go + have: v59.3.1+incompatible + want: v59.3.0+incompatible +golang.org/x/mod + have: v0.6.10-dev.0.20220419223038-86c51ed26bb4 + want: v0.6.0-dev.0.20220419223038-86c51ed26bb4 +`, + err: "3 incompatibilities found", + }, + "fix": { + goSum: "./testdata/changes-go.sum", + fix: true, + expected: `go mod edit --replace cloud.google.com/go=cloud.google.com/go@v0.100.2 +go mod edit --replace github.com/Azure/azure-sdk-for-go=github.com/Azure/azure-sdk-for-go@v59.3.0+incompatible +go get golang.org/x/mod@v0.6.0-dev.0.20220419223038-86c51ed26bb4 +`, + err: "3 incompatibilities found", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + buf.Reset() + + orig := goSum + goSum = tc.goSum + defer func() { goSum = orig }() + + fix := gogetEnabled + gogetEnabled = tc.fix + defer func() { gogetEnabled = fix }() + + err := pluginFunc(cmd, nil) + if tc.err != "" { + require.EqualError(t, err, tc.err) + } else { + require.NoError(t, err) + } + require.Equal(t, tc.expected, buf.String()) + }) + } +} diff --git a/root.go b/root.go index 0957bb1..6794588 100644 --- a/root.go +++ b/root.go @@ -66,7 +66,7 @@ var ( Use: "check-plugin", Short: "Checks your plugin dependencies are compatible.", Long: "Checks your plugin dependencies are compatible and proposes commands to update your dependencies.", - Run: pluginFunc, + RunE: pluginFunc, Example: "krakend check-plugin -g 1.19.0 -s ./go.sum -f", } @@ -106,7 +106,7 @@ func init() { portFlag := IntFlagBuilder(&port, "port", "p", 0, "Listening port for the http service") RunCommand = NewCommand(runCmd, cfgFlag, debugFlag, portFlag) - goSumFlag := StringFlagBuilder(&goSum, "sum", "s", goSum, "Path to the go.sum file to analize") + goSumFlag := StringFlagBuilder(&goSum, "sum", "s", goSum, "Path to the go.sum file to analyze") goVersionFlag := StringFlagBuilder(&goVersion, "go", "g", goVersion, "The version of the go compiler used for your plugin") libcVersionFlag := StringFlagBuilder(&libcVersion, "libc", "l", "", "Version of the libc library used") gogetFlag := BoolFlagBuilder(&gogetEnabled, "format", "f", false, "Shows fix commands to update your dependencies") diff --git a/testdata/changes-go.sum b/testdata/changes-go.sum new file mode 100644 index 0000000..2c84478 --- /dev/null +++ b/testdata/changes-go.sum @@ -0,0 +1,12 @@ +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.3 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= +cloud.google.com/go v0.100.3/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v59.3.1+incompatible h1:dPIm0BO4jsMXFcCI/sLTPkBtE7mk8WMuRHA0JeWhlcQ= +github.com/Azure/azure-sdk-for-go v59.3.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.10-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.10-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= diff --git a/testdata/go.mod b/testdata/go.mod new file mode 100644 index 0000000..c429cbf --- /dev/null +++ b/testdata/go.mod @@ -0,0 +1,13 @@ +module github.com/krakendio/krakend-cobra/v2 + +go 1.17 + +require ( + github.com/gin-gonic/gin v1.8.2 + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 +) + +require ( + cloud.google.com/go v0.100.2 // indirect + github.com/Azure/azure-sdk-for-go v59.3.0+incompatible // indirect +) diff --git a/testdata/match-go.sum b/testdata/match-go.sum new file mode 100644 index 0000000..475b6d0 --- /dev/null +++ b/testdata/match-go.sum @@ -0,0 +1,12 @@ +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v59.3.0+incompatible h1:dPIm0BO4jsMXFcCI/sLTPkBtE7mk8WMuRHA0JeWhlcQ= +github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=