diff --git a/.ferconfig.json.example b/.ferconfig.json.example new file mode 100644 index 0000000..f99da7d --- /dev/null +++ b/.ferconfig.json.example @@ -0,0 +1,6 @@ +{ + "gocek": { + "project_directories": [], + "output_save_folder": "" + } +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ea19da..0c9a61a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,24 @@ #Fer + +## [v1.6.0] - 2020-04-16 +### New Features +- add fer & gocek config +- add gocek all to check all modules provided +- add gocek to console +- add checker +- add mod model + +### Other Improvements +- add .ferconfig.json.example & update README +- remove debug log +- add .ferconfig.json example + + ## [v1.5.4] - 2020-04-14 ### Fixes -- fix self update +- self update ([#17](https://github.com/kumparan/fer/issues/17)) @@ -84,7 +99,8 @@ - db migrationfile generator ([#3](https://github.com/kumparan/fer/issues/3)) -[Unreleased]: https://github.com/kumparan/fer/compare/v1.5.4...HEAD +[Unreleased]: https://github.com/kumparan/fer/compare/v1.6.0...HEAD +[v1.6.0]: https://github.com/kumparan/fer/compare/v1.5.4...v1.6.0 [v1.5.4]: https://github.com/kumparan/fer/compare/v1.5.3...v1.5.4 [v1.5.3]: https://github.com/kumparan/fer/compare/v1.5.2...v1.5.3 [v1.5.2]: https://github.com/kumparan/fer/compare/v1.5.1...v1.5.2 diff --git a/README.md b/README.md index d83cf5f..51da143 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,13 @@ fer version - [x] Generate Service&test files and Client files From Proto - [x] DB migration file generator - [x] Generate Repository (include model) +- [x] Gocek, checks go mod update info - [ ] Add worker with command - [ ] Add Nats Subscriber with command +## Config +Add `.ferconfig.json` into `$HOME/.ferconfig.json`, use the `.ferconfig.json.example` as reference + ## Kumparan Microservices Generator `fer generate project content-service --proto pb/example/example.proto` @@ -99,3 +103,9 @@ for deployment, push deployment tag `fer create chglog vx.x.x` to generate CHANGELOG.md +## Gocek +`fer gocek` +check current working directory + +`fer gocek all` +check all directory listed in `.ferconfig.json` \ No newline at end of file diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..bdb2ef8 --- /dev/null +++ b/config/config.go @@ -0,0 +1,11 @@ +package config + +import ( + "os" + "path/filepath" +) + +// FerConfigPath :nodoc: +func FerConfigPath() string { + return filepath.Join(os.Getenv("HOME"), ".ferconfig.json") +} diff --git a/config/default.go b/config/default.go index e95029a..8998f67 100644 --- a/config/default.go +++ b/config/default.go @@ -18,7 +18,8 @@ const ( RichgoInstallerURL = "github.com/kyoh86/richgo" // GolintInstallerURL :nodoc: GolintInstallerURL = "github.com/golangci/golangci-lint/cmd/golangci-lint" - ProtobufVersion = "3.7.1" + // ProtobufVersion :nodoc: + ProtobufVersion = "3.7.1" // ProtobufOSXInstallerURL :nodoc: ProtobufOSXInstallerURL = "https://github.com/protocolbuffers/protobuf/releases/download/v3.7.1/protoc-3.7.1-osx-x86_64.zip" // ProtobufLinuxInstallerURL :nodoc: diff --git a/config/ferconfig.go b/config/ferconfig.go new file mode 100644 index 0000000..239a90a --- /dev/null +++ b/config/ferconfig.go @@ -0,0 +1,69 @@ +package config + +import ( + "encoding/json" + "io/ioutil" + "os" + "sync" + + log "github.com/sirupsen/logrus" +) + +var cfg FerConfig +var once sync.Once + +// GocekConfig :nodoc: +type GocekConfig struct { + ProjectDirs []string `json:"project_directories"` + SaveOutputDir string `json:"output_save_folder"` +} + +// FerConfig :nodoc: +type FerConfig struct { + Gocek GocekConfig `json:"gocek"` +} + +func init() { + once.Do(func() { + loadCfg() + }) +} + +func loadCfg() { + cfgPath := FerConfigPath() + _, err := os.Stat(cfgPath) + switch { + case os.IsNotExist(err): + f, err := os.Create(cfgPath) + if err != nil { + log.Fatal(err) + } + defer f.Close() + + cfg.Gocek.ProjectDirs = make([]string, 0) + bt, _ := json.MarshalIndent(cfg, "", " ") + _, err = f.Write([]byte(bt)) + if err != nil { + log.Fatal(err) + } + + case err == nil: + b, err := ioutil.ReadFile(FerConfigPath()) + if err != nil { + log.Fatal(err) + } + + err = json.Unmarshal(b, &cfg) + if err != nil { + log.Fatal(err) + } + + default: + log.Fatal(err) + } +} + +// GetFerConfig :nodoc: +func GetFerConfig() FerConfig { + return cfg +} diff --git a/config/version.go b/config/version.go index b12b6b7..915eeb0 100644 --- a/config/version.go +++ b/config/version.go @@ -1,4 +1,4 @@ package config // Version define version of fer -const Version = "v1.5.4" +const Version = "v1.6.0" diff --git a/console/gocek.go b/console/gocek.go new file mode 100644 index 0000000..9d3a3f1 --- /dev/null +++ b/console/gocek.go @@ -0,0 +1,39 @@ +package console + +import ( + "github.com/kumparan/fer/config" + "github.com/kumparan/fer/gocek" + "github.com/spf13/cobra" +) + +func init() { + gocekCmd.AddCommand(gocekAllCmd) + rootCmd.AddCommand(gocekCmd) +} + +var gocekCmd = &cobra.Command{ + Use: "gocek", + Short: "gocek check a module update info", + Long: `default check current working directory`, + Run: gocekCWD, +} + +var gocekAllCmd = &cobra.Command{ + Use: "all", + Short: "check all module update info", + Run: gocekAll, +} + +func gocekAll(cmd *cobra.Command, args []string) { + cfg := config.GetFerConfig() + checker := gocek.ModuleChecker{ + RootDir: cfg.Gocek.SaveOutputDir, + } + + checker.Checks(cfg.Gocek.ProjectDirs) +} + +func gocekCWD(cmd *cobra.Command, args []string) { + checker := gocek.ModuleChecker{} + checker.CheckCWD() +} diff --git a/go.mod b/go.mod index a1dc256..6c8ee2f 100644 --- a/go.mod +++ b/go.mod @@ -10,9 +10,10 @@ require ( github.com/hashicorp/go-version v1.2.0 github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365 github.com/kr/pretty v0.1.0 // indirect - github.com/magiconair/properties v1.8.0 github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-isatty v0.0.10 // indirect + github.com/olekukonko/tablewriter v0.0.4 + github.com/sirupsen/logrus v1.5.0 github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index 6d768cf..f91c5b8 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365 h1:ECW73yc9MY79 github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -45,15 +47,21 @@ github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW1 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= @@ -78,6 +86,7 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191009170203-06d7bd2c5f4f h1:hjzMYz/7Ea1mNKfOnFOfktR0mlA5jqhvywClCMHM/qw= diff --git a/gocek/checker.go b/gocek/checker.go new file mode 100644 index 0000000..910c44e --- /dev/null +++ b/gocek/checker.go @@ -0,0 +1,205 @@ +package gocek + +import ( + "encoding/json" + "fmt" + "os" + "os/exec" + "path" + "strings" + "sync" + "time" + + "github.com/olekukonko/tablewriter" + log "github.com/sirupsen/logrus" +) + +const ( + _defaultMaxQueueSize = int(10) +) + +// ModuleChecker :nodoc: +type ModuleChecker struct { + RootDir string +} + +// CheckCWD call CheckCWD() and print into stdout +func (mc *ModuleChecker) CheckCWD() { + modlist, err := CheckCWD() + if err != nil { + log.Fatal(err) + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Path", "Version", "Next Version"}) + + for _, v := range modlist { + table.Append([]string{v.Path, v.Version, v.NextVersion}) + } + table.Render() // Send output +} + +// Checks check module projects and save the json file +func (mc *ModuleChecker) Checks(dirs []string) { + for _, dir := range dirs { + os.Chdir(dir) + modules, err := CheckCWD() + if err != nil { + continue + } + + sdir := strings.Split(dir, "/") + err = mc.save(sdir[len(sdir)-1], modules) + if err != nil { + log.Error(err) + return + } + os.Chdir(mc.RootDir) + } +} + +// save modules as json +func (mc *ModuleChecker) save(modName string, modules []*SimpleModule) error { + now := time.Now() + layout := "2006-01-02" + fileName := fmt.Sprintf("%s.%s.json", modName, now.Format(layout)) + dst := path.Join(mc.RootDir, fileName) + + f, err := os.Create(dst) + if err != nil { + log.Error(err) + return err + } + defer f.Close() + + log.Infof("saving %s", fileName) + + j, err := json.MarshalIndent(modules, " ", " ") + if err != nil { + return err + } + + _, err = f.Write([]byte(j)) + return err +} + +// CheckCWD check current working directory +func CheckCWD() (modules []*SimpleModule, err error) { + modList, err := findDirectModList() + if err != nil { + log.Error(err) + return + } + + return findAllModuleUpdate(modList) +} + +func findAllModuleUpdate(mods []string) (modules []*SimpleModule, err error) { + queue := make(map[int][]string) + count := 0 + + // group the module per QueueSize, + // so each group can be conccurently execute + for _, m := range mods { + queue[count] = append(queue[count], m) + if len(queue[count]) == _defaultMaxQueueSize { + count++ + } + } + + modsCh := make(chan *Module, len(mods)) + for _, q := range queue { + wg := sync.WaitGroup{} + + wg.Add(len(q)) + for _, m := range q { + go func(m string) { + defer wg.Done() + + mod, err := findModuleUpdate(m) + if err != nil { + log.Error(err) + return + } + + modsCh <- mod + }(m) + } + + wg.Wait() + } + + close(modsCh) + for module := range modsCh { + if module.Update.Version == "" { + continue + } + modules = append(modules, &SimpleModule{ + Path: module.Path, + Version: module.Version, + NextVersion: module.Update.Version, + }) + } + + return +} + +func findModuleUpdate(modName string) (*Module, error) { + var err error + out, err := exec.Command("go", "list", "-m", "-u", "-json", modName).Output() + if err != nil { + log.WithField("mod", modName).Error(err) + return nil, err + } + + m := Module{} + if err = json.Unmarshal(out, &m); err != nil { + log.WithField("out", string(out)).Error(err) + return nil, err + } + + return &m, nil +} + +func findModList() ([]string, error) { + out, err := exec.Command("go", "list", "-m", "all").Output() + if err != nil { + return nil, err + } + splitted := strings.Split(string(out), "\n") + + var list []string + for _, s := range splitted { + ss := sanitize(s) + if ss == "" { + continue + } + list = append(list, ss) + } + + return list, nil +} + +func findDirectModList() ([]string, error) { + out, err := exec.Command("go", "list", "-m", "-f", `{{ .Path }} | {{ .Indirect }}`, "all").Output() + if err != nil { + return nil, err + } + splitted := strings.Split(string(out), "\n") + + var list []string + for _, ss := range splitted { + if strings.Trim(ss, " ") == "" || strings.Contains(ss, "true") { + continue + } + + list = append(list, strings.Split(ss, " | ")[0]) + } + + return list, nil +} + +func sanitize(raw string) string { + clean := strings.Trim(raw, " ") + return strings.Split(clean, " ")[0] +} diff --git a/gocek/mod.go b/gocek/mod.go new file mode 100644 index 0000000..c19719b --- /dev/null +++ b/gocek/mod.go @@ -0,0 +1,25 @@ +package gocek + +import "time" + +// Module :nodoc: +type Module struct { + Path string `json:"Path"` + Version string `json:"Version"` + Time time.Time `json:"Time"` + Update struct { + Path string `json:"Path"` + Version string `json:"Version"` + Time time.Time `json:"Time"` + } `json:"Update"` + Indirect bool `json:"Indirect"` + Dir string `json:"Dir"` + GoMod string `json:"GoMod"` +} + +// SimpleModule :nodoc: +type SimpleModule struct { + Path string `json:"path"` + Version string `json:"version"` + NextVersion string `json:"next_version"` +}