diff --git a/.golangci.yml b/.golangci.yml index 061c2fb..6f2a951 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -74,7 +74,7 @@ linters: - goconst # finds repeated strings that could be replaced by a constant - godot - godox # tool for detection of FIXME, TODO and other comment keywords - - gofumpt + - gofmt - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports - gomoddirectives # manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. - gomodguard # check for blocked dependencies diff --git a/README.md b/README.md index e21623a..1ab6af1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -# Gouick (name TBD) +# Gouick -Only tested on Linux - -## Todo -- Implement for MacOS & Windows \ No newline at end of file +[![Build Status](https://drone.home.m-and-m.ovh/api/badges/mderasse/gouick/status.svg?ref=refs/heads/main)](https://drone.home.m-and-m.ovh/mderasse/gouick) diff --git a/cmd/generate.go b/cmd/generate.go index 012505b..065eb68 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -10,14 +10,13 @@ import ( "github.com/spf13/cobra" ) -// generateCmd represents the generate command +// generateCmd represents the generate command. var generateCmd = &cobra.Command{ Use: "generate", Short: "Generate all the files (API, DB, ...)", Long: `Generate will automatically create all the files of the Project. You can also generate files for each 'modules' by using the command bellows`, Run: func(cmd *cobra.Command, args []string) { - fmt.Println("generate called") }, } diff --git a/cmd/generateAPI.go b/cmd/generateAPI.go index 4551262..ca2e9ea 100644 --- a/cmd/generateAPI.go +++ b/cmd/generateAPI.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" ) -// apiCmd represents the api command +// apiCmd represents the api command. var apiCmd = &cobra.Command{ Use: "api", Short: "Generate Go-Swagger Files and Controllers", diff --git a/cmd/generateDB.go b/cmd/generateDB.go index ec24d05..ee3b308 100644 --- a/cmd/generateDB.go +++ b/cmd/generateDB.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" ) -// dbCmd represents the db command +// dbCmd represents the db command. var dbCmd = &cobra.Command{ Use: "db", Short: "Generate Database Struct from pkg/dbmodels/schema.yaml", diff --git a/cmd/generateDocker.go b/cmd/generateDocker.go index 5f6dc01..a2852ca 100644 --- a/cmd/generateDocker.go +++ b/cmd/generateDocker.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" ) -// dockerCmd represents the docker command +// dockerCmd represents the docker command. var dockerCmd = &cobra.Command{ Use: "docker", Short: "Generate Dockerfile for API & Workers", diff --git a/cmd/generateHelm.go b/cmd/generateHelm.go index bcd10ec..ac55c98 100644 --- a/cmd/generateHelm.go +++ b/cmd/generateHelm.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" ) -// helmCmd represents the helm command +// helmCmd represents the helm command. var helmCmd = &cobra.Command{ Use: "helm", Short: "Generate Kubernetes Helm chart for API & Workers", diff --git a/cmd/generateHelp.go b/cmd/generateHelp.go index 141f97b..0b8561c 100644 --- a/cmd/generateHelp.go +++ b/cmd/generateHelp.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/cobra" ) -// helpCmd represents the help command +// helpCmd represents the help command. var helpCmd = &cobra.Command{ Use: "help", Short: "Show that help", diff --git a/cmd/generateLauncher.go b/cmd/generateLauncher.go index 471ddaf..7024068 100644 --- a/cmd/generateLauncher.go +++ b/cmd/generateLauncher.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" ) -// launcherCmd represents the launcher command +// launcherCmd represents the launcher command. var launcherCmd = &cobra.Command{ Use: "launcher", Short: "Generate Launcher Bash script", diff --git a/cmd/generateMakefile.go b/cmd/generateMakefile.go index bccfb1a..25a0e86 100644 --- a/cmd/generateMakefile.go +++ b/cmd/generateMakefile.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" ) -// makefileCmd represents the makefile command +// makefileCmd represents the makefile command. var makefileCmd = &cobra.Command{ Use: "makefile", Short: "Generate Makefile", diff --git a/cmd/init.go b/cmd/init.go index 2b34578..b518075 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -19,7 +19,7 @@ import ( log "github.com/sirupsen/logrus" ) -// initCmd represents the init command +// initCmd represents the init command. var initCmd = &cobra.Command{ Use: "init", Short: "Initialize a new project", @@ -39,11 +39,10 @@ func init() { // runInitAction // Steps: -// 1 - Check that we have the dependencies -// 2 - Check that we are not in project directory -// 3 - Ask info to the user +// 1 - Check that we have the dependencies. +// 2 - Check that we are not in project directory. +// 3 - Ask info to the user. func runInitAction(cmd *cobra.Command, args []string) { - log.Debugf("Starting command Init") log.Debugf("Checking dependencies") @@ -90,7 +89,7 @@ func runInitAction(cmd *cobra.Command, args []string) { } // ask which API type we want to use - var possibleAPITypes []string + possibleAPITypes := make([]string, 0) for _, apiType := range models.GetListOfAPITypeName() { possibleAPITypes = append(possibleAPITypes, string(apiType)) } diff --git a/cmd/root.go b/cmd/root.go index 19dffd2..496370e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,7 +13,8 @@ import ( log "github.com/sirupsen/logrus" ) -var version = "0.0.1" +const version = "0.0.1" + var verbose = false var acceptAll = false var dependencyList = []dependencies.DependencyInterface{ @@ -22,7 +23,7 @@ var dependencyList = []dependencies.DependencyInterface{ dependencies.Swagger{}, } -// rootCmd represents the base command when called without any subcommands +// rootCmd represents the base command when called without any subcommands. var rootCmd = &cobra.Command{ Use: "boot", Short: "Boot is a toolbox app to bootstrap quickly a Go-Swagger API", @@ -32,7 +33,7 @@ It will: - Auto-Update - Download dependencies (go-swagger, ...) - Initialize an API with default middlewares - - Generate Models / Controlers + - Generate Models / Controllers - Generate Database structs - ....`, @@ -46,8 +47,7 @@ It will: // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { - err := rootCmd.Execute() - if err != nil { + if err := rootCmd.Execute(); err != nil { os.Exit(1) } } @@ -57,11 +57,9 @@ func init() { rootCmd.PersistentFlags().BoolVarP(&acceptAll, "yes", "y", false, "accept all change, disable interactive process") } -// checkDependencies +// checkDependencies will go through all the dependencies a check if they are installed and respect minimum version. func checkDependencies() bool { - for _, dependency := range dependencyList { - isSupported, err := dependency.IsVersionSupported() if err != nil { log.Errorf("Fail to check %s version. The following error happen: %s", dependency.GetName(), err.Error()) diff --git a/cmd/test.go b/cmd/test.go index c86ab8d..1ab3439 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" ) -// testCmd represents the test command +// testCmd represents the test command. var testCmd = &cobra.Command{ Use: "test", Short: "Launch Test", diff --git a/cmd/upgrade.go b/cmd/upgrade.go index c218b21..7a749f6 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -12,7 +12,7 @@ import ( log "github.com/sirupsen/logrus" ) -// upgradeCmd represents the upgrade command +// upgradeCmd represents the upgrade command. var upgradeCmd = &cobra.Command{ Use: "upgrade", Short: "Upgrade all development dependencies", @@ -29,11 +29,9 @@ func init() { } func runUpgradeAction(cmd *cobra.Command, args []string) { - log.Debug("Starting Upgrade command") for _, dependency := range dependencyList { - log.Debugf("Checking if dependency %s is supported", dependency.GetName()) isSupported, err := dependency.IsVersionSupported() @@ -81,7 +79,6 @@ func runUpgradeAction(cmd *cobra.Command, args []string) { log.Infof("Where do you want to install %s ?", dependency.GetName()) installDirectory = helpers.PathInput() } - } else { log.Infof("Installing %s in the following directory: %s", dependency.GetName(), installDirectory) } @@ -91,7 +88,7 @@ func runUpgradeAction(cmd *cobra.Command, args []string) { // check if the path is ok, else ask user for new path if we are in a interactive mode, else stop _, err = helpers.CheckAndCreateDir(installDirectory) if err != nil { - log.Errorf("Impossible to create the instalation directory for %s", dependency.GetName()) + log.Errorf("Impossible to create the installation directory for %s", dependency.GetName()) if acceptAll { return } diff --git a/cmd/version.go b/cmd/version.go index 2030262..0fc55f1 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" ) -// versionCmd represents the version command +// versionCmd represents the version command. var versionCmd = &cobra.Command{ Use: "version", Short: "Show gouick version", diff --git a/helpers/api_types/api_type.go b/helpers/api_types/api_type.go index 0b0545f..d58a337 100644 --- a/helpers/api_types/api_type.go +++ b/helpers/api_types/api_type.go @@ -9,6 +9,7 @@ import ( "git.home.m-and-m.ovh/mderasse/gouick/helpers/models" ) +// GetAPIType will return the APIType object based on an APITypeName. func GetAPIType(in models.APITypeName) (APITypeInterface, error) { if in == "" { return nil, errors.BadRequestf("missing parameter") @@ -25,6 +26,8 @@ func GetAPIType(in models.APITypeName) (APITypeInterface, error) { return go_swagger.APIType{}, nil case models.APITypeName_MOJOLICIOUS: return mojolicious.APIType{}, nil + case models.APITypeName_NULL: + break } return nil, errors.NotFoundf("Unknown Api Type") diff --git a/helpers/api_types/base/api_type.go b/helpers/api_types/base/api_type.go index 0303fea..0a752c0 100644 --- a/helpers/api_types/base/api_type.go +++ b/helpers/api_types/base/api_type.go @@ -1,5 +1,5 @@ package base -// APIType +// APIType struct. type APIType struct { } diff --git a/helpers/api_types/base/check_initialize.go b/helpers/api_types/base/check_initialize.go index 4289696..b7bc321 100644 --- a/helpers/api_types/base/check_initialize.go +++ b/helpers/api_types/base/check_initialize.go @@ -4,8 +4,7 @@ import ( "github.com/juju/errors" ) -// CheckInitialize +// CheckInitialize will do preliminary check before initializing a new project. func (a APIType) CheckInitialize() error { - return errors.NotImplementedf("CheckInitialize not implemented for %s", a.GetName()) } diff --git a/helpers/api_types/base/get_name.go b/helpers/api_types/base/get_name.go index 846d711..c127f56 100644 --- a/helpers/api_types/base/get_name.go +++ b/helpers/api_types/base/get_name.go @@ -4,6 +4,7 @@ import ( "git.home.m-and-m.ovh/mderasse/gouick/helpers/models" ) +// GetName return the APITypeName of the APIType. func (a APIType) GetName() models.APITypeName { return models.APITypeName_NULL } diff --git a/helpers/api_types/base/get_user_input.go b/helpers/api_types/base/get_user_input.go index 7049968..0676e18 100644 --- a/helpers/api_types/base/get_user_input.go +++ b/helpers/api_types/base/get_user_input.go @@ -6,7 +6,7 @@ import ( "github.com/juju/errors" ) +// GetInitializeUserInput will ask user to provide information that allow initialization of a project. func (a APIType) GetInitializeUserInput(params *models.UserInputParams) (*models.UserInputParams, error) { - return nil, errors.NotImplementedf("GetInitializeUserInput not implemented for %s", a.GetName()) } diff --git a/helpers/api_types/gin_gonic/get_name.go b/helpers/api_types/gin_gonic/get_name.go index b0ab2f7..1f13a94 100644 --- a/helpers/api_types/gin_gonic/get_name.go +++ b/helpers/api_types/gin_gonic/get_name.go @@ -4,6 +4,7 @@ import ( "git.home.m-and-m.ovh/mderasse/gouick/helpers/models" ) +// GetName return the APITypeName of the APIType. func (a APIType) GetName() models.APITypeName { return models.APITypeName_GIN_GONIC } diff --git a/helpers/api_types/go_swagger/check_initialize.go b/helpers/api_types/go_swagger/check_initialize.go index 5977501..f034b66 100644 --- a/helpers/api_types/go_swagger/check_initialize.go +++ b/helpers/api_types/go_swagger/check_initialize.go @@ -10,7 +10,7 @@ import ( log "github.com/sirupsen/logrus" ) -// CheckInitialize +// CheckInitialize will do preliminary check before initializing a new project. func (a APIType) CheckInitialize() error { log.Debugf("Starting %s check initialize", string(a.GetName())) diff --git a/helpers/api_types/go_swagger/get_name.go b/helpers/api_types/go_swagger/get_name.go index 05ee310..06f1479 100644 --- a/helpers/api_types/go_swagger/get_name.go +++ b/helpers/api_types/go_swagger/get_name.go @@ -4,6 +4,7 @@ import ( "git.home.m-and-m.ovh/mderasse/gouick/helpers/models" ) +// GetName return the APITypeName of the APIType. func (a APIType) GetName() models.APITypeName { return models.APITypeName_GO_SWAGGER } diff --git a/helpers/api_types/go_swagger/get_user_input.go b/helpers/api_types/go_swagger/get_user_input.go index f744927..46f57e2 100644 --- a/helpers/api_types/go_swagger/get_user_input.go +++ b/helpers/api_types/go_swagger/get_user_input.go @@ -11,7 +11,7 @@ import ( log "github.com/sirupsen/logrus" ) -// GetInitializeUserInput +// GetInitializeUserInput will ask user to provide information that allow initialization of a project. func (a APIType) GetInitializeUserInput(params *models.UserInputParams) (*models.UserInputParams, error) { log.Debugf("Starting %s user input", a.GetName()) diff --git a/helpers/api_types/mojolicious/get_name.go b/helpers/api_types/mojolicious/get_name.go index ed2c3a2..ef71511 100644 --- a/helpers/api_types/mojolicious/get_name.go +++ b/helpers/api_types/mojolicious/get_name.go @@ -4,6 +4,7 @@ import ( "git.home.m-and-m.ovh/mderasse/gouick/helpers/models" ) +// GetName return the APITypeName of the APIType. func (a APIType) GetName() models.APITypeName { return models.APITypeName_MOJOLICIOUS } diff --git a/helpers/dependencies/git.go b/helpers/dependencies/git.go index 033bb7c..30b51c5 100644 --- a/helpers/dependencies/git.go +++ b/helpers/dependencies/git.go @@ -3,28 +3,29 @@ package dependencies import ( "os/exec" + "git.home.m-and-m.ovh/mderasse/gouick/helpers/models" "github.com/juju/errors" log "github.com/sirupsen/logrus" ) +// Git represent an empty struct respecting the DependencyInterface. type Git struct{} // regroup all git dependencies function -// GetName -func (g Git) GetName() string { - return "Git" +// GetName return the name of the dependency using DependencyName enum. +func (g Git) GetName() models.DependencyName { + return models.DependencyName_GIT } -// GetMinimumVersion +// GetMinimumVersion return the minimum version required for that dependency. func (g Git) GetMinimumVersion() string { return "0.0.0" } -// IsInstalled +// IsInstalled check if the dependency is installed on the system. func (g Git) IsInstalled() (bool, error) { - _, err := g.GetBinaryPath() if err != nil && !errors.Is(err, exec.ErrNotFound) { return false, errors.Trace(err) @@ -35,7 +36,7 @@ func (g Git) IsInstalled() (bool, error) { return true, nil } -// GetBinaryPath +// GetBinaryPath will search for the binary and return the path if found. func (g Git) GetBinaryPath() (string, error) { log.Debug("looking for git binary") @@ -49,9 +50,8 @@ func (g Git) GetBinaryPath() (string, error) { return path, nil } -// GetVersion +// GetVersion will find the current version of the dependency. func (g Git) GetVersion() (string, error) { - isInstalled, err := g.IsInstalled() if err != nil { return "", errors.Trace(err) @@ -64,9 +64,8 @@ func (g Git) GetVersion() (string, error) { return "0.0.0", nil } -// IsVersionSupported +// IsVersionSupported will compare the current version with the minimum expected version. func (g Git) IsVersionSupported() (bool, error) { - isInstalled, err := g.IsInstalled() if err != nil { return false, errors.Trace(err) @@ -78,32 +77,32 @@ func (g Git) IsVersionSupported() (bool, error) { return true, nil } -// CanBeInstalled +// CanBeInstalled define if the dependency installation is handled by the app. func (g Git) CanBeInstalled() bool { return false } -// DescribeInstall +// DescribeInstall will do nothing for that dependency. func (g Git) DescribeInstall(path string) string { return "" } -// Install +// Install will do nothing for that dependency. func (g Git) Install(path string) error { return nil } -// DescribePostInstall +// DescribePostInstall will do nothing for that dependency. func (g Git) DescribePostInstall(path string) string { return "" } -// PostInstall +// PostInstall will do nothing for that dependency. func (g Git) PostInstall(path string) error { return nil } -// GetInstallDirectory +// GetInstallDirectory will do nothing for that dependency. func (g Git) GetInstallDirectory() (string, error) { return "", nil } diff --git a/helpers/dependencies/golang.go b/helpers/dependencies/golang.go index 56bdd08..638fea0 100644 --- a/helpers/dependencies/golang.go +++ b/helpers/dependencies/golang.go @@ -13,36 +13,37 @@ import ( "github.com/juju/errors" "git.home.m-and-m.ovh/mderasse/gouick/helpers" + "git.home.m-and-m.ovh/mderasse/gouick/helpers/models" log "github.com/sirupsen/logrus" ) -// minimum minor required for the app to work +// minimumGolangVersion is the minimum minor required for the app to work. const minimumGolangVersion = "1.18.4" // installation directory for fresh install. -// will be prefixed by $HOME +// will be prefixed by $HOME. const defaultGolangInstallDir = "/local/go" var regexGolangVersion = regexp.MustCompile(`^go version go(\d+\.\d+\.\d+)`) +// Golang represent an empty struct respecting the DependencyInterface. type Golang struct{} -// regroup all golang dependencies function +// regroup all golang dependencies function. -// GetName -func (g Golang) GetName() string { - return "Golang" +// GetName return the name of the dependency using DependencyName enum. +func (g Golang) GetName() models.DependencyName { + return models.DependencyName_GOLANG } -// GetMinimumVersion +// GetMinimumVersion return the minimum version required for that dependency. func (g Golang) GetMinimumVersion() string { return minimumGolangVersion } -// IsInstalled +// IsInstalled check if the dependency is installed on the system. func (g Golang) IsInstalled() (bool, error) { - _, err := g.GetBinaryPath() if err != nil && !errors.Is(err, exec.ErrNotFound) { return false, errors.Trace(err) @@ -53,7 +54,7 @@ func (g Golang) IsInstalled() (bool, error) { return true, nil } -// GetBinaryPath +// GetBinaryPath will search for the binary and return the path if found. func (g Golang) GetBinaryPath() (string, error) { log.Debug("looking for golang binary") @@ -67,7 +68,7 @@ func (g Golang) GetBinaryPath() (string, error) { return path, nil } -// GetVersion +// GetVersion will find the current version of the dependency. func (g Golang) GetVersion() (string, error) { isInstalled, err := g.IsInstalled() @@ -86,6 +87,7 @@ func (g Golang) GetVersion() (string, error) { log.Debug("executing go version command") + //nolint:gosec // we trust the binary we are launching cmd := exec.Command(binaryPath, "version") stdout, err := cmd.Output() if err != nil { @@ -103,9 +105,8 @@ func (g Golang) GetVersion() (string, error) { return parseOutput[1], nil } -// IsVersionSupported +// IsVersionSupported will compare the current version with the minimum expected version. func (g Golang) IsVersionSupported() (bool, error) { - isInstalled, err := g.IsInstalled() if err != nil { return false, errors.Trace(err) @@ -136,14 +137,13 @@ func (g Golang) IsVersionSupported() (bool, error) { return true, nil } -// CanBeInstalled +// CanBeInstalled define if the dependency installation is handled by the app. func (g Golang) CanBeInstalled() bool { return true } -// DescribeInstall +// DescribeInstall will list the aciton that will be executed for the dependency update or installation. func (g Golang) DescribeInstall(path string) string { - commands := []string{ "The following commands will be executed", fmt.Sprintf("rm -rf %s/* ", path), @@ -153,9 +153,8 @@ func (g Golang) DescribeInstall(path string) string { return strings.Join(commands, "\n") } -// Install +// Install will install or update the dependency. func (g Golang) Install(path string) error { - err := helpers.RemoveDirectoryContent(path) if err != nil { log.Warnf("fail to delete content of directory %s", path) @@ -170,7 +169,6 @@ func (g Golang) Install(path string) error { // zip on windows, tar gz on other platform if runtime.GOOS == "windows" { - log.Debug("Working on zip, Unzip") err = unZip(content, "go/", path) @@ -178,9 +176,7 @@ func (g Golang) Install(path string) error { log.Warnf("fail to un-zip downloaded file from %s", downloadURL) return errors.Trace(err) } - } else { - log.Debug("Working on tar gz, UnGzip & unTar") gzipReader, err := unGzip(content) @@ -199,9 +195,8 @@ func (g Golang) Install(path string) error { return nil } -// DescribePostInstall +// DescribePostInstall will list the post installation action that will be executed. func (g Golang) DescribePostInstall(path string) string { - descriptions := []string{ `For your environment to work correctly, we will add if needed the following environment variable to your .bashrc: @@ -219,9 +214,8 @@ You will have to reopen a new terminal to apply the changes or execute the follo return strings.Join(descriptions, "\n") } -// PostInstall +// PostInstall will execute the post installation or update of the dependency. func (g Golang) PostInstall(path string) error { - if runtime.GOOS == "windows" { log.Warnf("Unable to environement variable on windows. Please add it by yourself") return nil @@ -231,15 +225,13 @@ func (g Golang) PostInstall(path string) error { "# Golang - Added by gouick", } - gopath := os.Getenv("GOPATH") createGoPath := false - if gopath == "" { + if os.Getenv("GOPATH") == "" { lineBashRc = append(lineBashRc, "export GOPATH=\"$HOME/go\"") createGoPath = true } - goroot := os.Getenv("GOROOT") - if goroot == "" { + if os.Getenv("GOROOT") == "" { lineBashRc = append(lineBashRc, fmt.Sprintf("export GOROOT=\"%s\"", path)) } @@ -265,12 +257,17 @@ func (g Golang) PostInstall(path string) error { fh, err := os.OpenFile( filepath.Join(homeDir, ".bashrc"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, - 0644, + 0600, ) if err != nil { return errors.Trace(err) } - defer fh.Close() + + defer func() { + if err := fh.Close(); err != nil { + log.Errorf("Error closing file: %s", err) + } + }() _, err = fh.WriteString( fmt.Sprintf("\n\n%s\n", strings.Join(lineBashRc, "\n")), @@ -281,7 +278,6 @@ func (g Golang) PostInstall(path string) error { } if createGoPath { - log.Debug("creating gopath directory") _, err = helpers.CheckAndCreateDir(filepath.Join(homeDir, "go")) @@ -295,9 +291,8 @@ func (g Golang) PostInstall(path string) error { // GetInstallDirectory will try to find the current golang directory. If it doesn't exist or if it's in a // not userspace directory, it will provide the "default" -// It doesn't mean that the directory is "writable" +// It doesn't mean that the directory is "writable". func (g Golang) GetInstallDirectory() (string, error) { - homeDir, err := os.UserHomeDir() if err != nil { return "", errors.Trace(err) @@ -321,6 +316,7 @@ func (g Golang) GetInstallDirectory() (string, error) { log.Debug("executing go env GOROOT command") + //nolint:gosec // we trust the binary we are launching cmd := exec.Command(binaryPath, "env", "GOROOT") stdout, err := cmd.Output() if err != nil { @@ -336,10 +332,9 @@ func (g Golang) GetInstallDirectory() (string, error) { } func (g Golang) getDownloadURL() string { - if runtime.GOOS == "windows" { return fmt.Sprintf("https://dl.google.com/go/go%s.%s-%s.zip", minimumGolangVersion, runtime.GOOS, runtime.GOARCH) - } else { - return fmt.Sprintf("https://dl.google.com/go/go%s.%s-%s.tar.gz", minimumGolangVersion, runtime.GOOS, runtime.GOARCH) } + + return fmt.Sprintf("https://dl.google.com/go/go%s.%s-%s.tar.gz", minimumGolangVersion, runtime.GOOS, runtime.GOARCH) } diff --git a/helpers/dependencies/interface.go b/helpers/dependencies/interface.go index 06cdde9..13b2aa5 100644 --- a/helpers/dependencies/interface.go +++ b/helpers/dependencies/interface.go @@ -1,13 +1,15 @@ package dependencies -// DependencyInterface +import "git.home.m-and-m.ovh/mderasse/gouick/helpers/models" + +// DependencyInterface is the interface that need to be respected for a dependency. type DependencyInterface interface { CanBeInstalled() bool DescribeInstall(path string) string DescribePostInstall(path string) string GetBinaryPath() (string, error) GetInstallDirectory() (string, error) - GetName() string + GetName() models.DependencyName GetMinimumVersion() string GetVersion() (string, error) Install(path string) error diff --git a/helpers/dependencies/swagger.go b/helpers/dependencies/swagger.go index 2b363c8..360d17a 100644 --- a/helpers/dependencies/swagger.go +++ b/helpers/dependencies/swagger.go @@ -2,7 +2,7 @@ package dependencies import ( "fmt" - "io/ioutil" + "io" "os" "os/exec" "path/filepath" @@ -10,38 +10,39 @@ import ( "runtime" "strings" + "git.home.m-and-m.ovh/mderasse/gouick/helpers/models" "github.com/blang/semver" "github.com/juju/errors" log "github.com/sirupsen/logrus" ) -// minimum minor required for the app to work +// minimumGolangVersion is the minimum minor required for the app to work. const minimumSwaggerVersion = "0.29.0" // installation directory for fresh install. -// will be prefixed by $HOME +// will be prefixed by $HOME. const defaultSwaggerInstallDir = "/bin" var regexSwaggerVersion = regexp.MustCompile(`^version: v(\d+\.\d+\.\d+)`) +// Swagger represent an empty struct respecting the DependencyInterface. type Swagger struct{} -// regroup all swagger dependencies function +// regroup all swagger dependencies function. -// GetName -func (s Swagger) GetName() string { - return "Go Swagger" +// GetName return the name of the dependency using DependencyName enum. +func (s Swagger) GetName() models.DependencyName { + return models.DependencyName_GO_SWAGGER } -// GetMinimumVersion +// GetMinimumVersion return the minimum version required for that dependency. func (s Swagger) GetMinimumVersion() string { return minimumSwaggerVersion } -// IsInstalled +// IsInstalled check if the dependency is installed on the system. func (s Swagger) IsInstalled() (bool, error) { - _, err := s.GetBinaryPath() if err != nil && !errors.Is(err, exec.ErrNotFound) { return false, errors.Trace(err) @@ -52,7 +53,7 @@ func (s Swagger) IsInstalled() (bool, error) { return true, nil } -// GetBinaryPath +// GetBinaryPath will search for the binary and return the path if found. func (s Swagger) GetBinaryPath() (string, error) { log.Debug("looking for swagger binary") @@ -66,7 +67,7 @@ func (s Swagger) GetBinaryPath() (string, error) { return path, nil } -// GetVersion +// GetVersion will find the current version of the dependency. func (s Swagger) GetVersion() (string, error) { isInstalled, err := s.IsInstalled() @@ -85,6 +86,7 @@ func (s Swagger) GetVersion() (string, error) { log.Debug("executing swagger version command") + //nolint:gosec // we trust the binary we are launching cmd := exec.Command(binaryPath, "version") stdout, err := cmd.Output() if err != nil { @@ -102,9 +104,8 @@ func (s Swagger) GetVersion() (string, error) { return parseOutput[1], nil } -// IsVersionSupported +// IsVersionSupported will compare the current version with the minimum expected version. func (s Swagger) IsVersionSupported() (bool, error) { - isInstalled, err := s.IsInstalled() if err != nil { return false, errors.Trace(err) @@ -135,14 +136,13 @@ func (s Swagger) IsVersionSupported() (bool, error) { return true, nil } -// CanBeInstalled +// CanBeInstalled define if the dependency installation is handled by the app. func (s Swagger) CanBeInstalled() bool { return true } -// DescribeInstall +// DescribeInstall will list the aciton that will be executed for the dependency update or installation. func (s Swagger) DescribeInstall(path string) string { - commands := []string{ "The following commands will be executed", fmt.Sprintf("rm -rf %s/swagger ", path), @@ -153,9 +153,8 @@ func (s Swagger) DescribeInstall(path string) string { return strings.Join(commands, "\n") } -// Install +// Install will install or update the dependency. func (s Swagger) Install(path string) error { - downloadURL := s.getDownloadURL() content, err := downloadFile(downloadURL) if err != nil { @@ -166,14 +165,19 @@ func (s Swagger) Install(path string) error { fh, err := os.OpenFile( filepath.Join(path, "swagger"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, - 0744, + 0600, ) if err != nil { return errors.Trace(err) } - defer fh.Close() - bContent, err := ioutil.ReadAll(content) + defer func() { + if err := fh.Close(); err != nil { + log.Errorf("Error closing file: %s", err) + } + }() + + bContent, err := io.ReadAll(content) if err != nil { return errors.Trace(err) } @@ -186,9 +190,8 @@ func (s Swagger) Install(path string) error { return nil } -// DescribePostInstall +// DescribePostInstall will list the post installation action that will be executed. func (s Swagger) DescribePostInstall(path string) string { - return `For your environment to work correctly, we will add if needed the following environment variable to your .bashrc: PATH=\"$HOME/bin:$PATH\" @@ -200,9 +203,8 @@ You will have to reopen a new terminal to apply the changes or execute the follo ` } -// PostInstall +// PostInstall will execute the post installation or update of the dependency. func (s Swagger) PostInstall(path string) error { - lineBashRc := []string{ "# Swagger - Added by gouick", } @@ -229,12 +231,17 @@ func (s Swagger) PostInstall(path string) error { fh, err := os.OpenFile( filepath.Join(homeDir, ".bashrc"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, - 0644, + 0600, ) if err != nil { return errors.Trace(err) } - defer fh.Close() + + defer func() { + if err := fh.Close(); err != nil { + log.Errorf("Error closing file: %s", err) + } + }() _, err = fh.WriteString( fmt.Sprintf("\n\n%s\n", strings.Join(lineBashRc, "\n")), @@ -249,9 +256,8 @@ func (s Swagger) PostInstall(path string) error { // GetInstallDirectory will try to find the current swagger directory. If it doesn't exist or if it's in a // not userspace directory, it will provide the "default" -// It doesn't mean that the directory is "writable" +// It doesn't mean that the directory is "writable". func (s Swagger) GetInstallDirectory() (string, error) { - homeDir, err := os.UserHomeDir() if err != nil { return "", errors.Trace(err) @@ -277,6 +283,5 @@ func (s Swagger) GetInstallDirectory() (string, error) { } func (s Swagger) getDownloadURL() string { - return fmt.Sprintf("https://github.com/go-swagger/go-swagger/releases/download/v%s/swagger_%s_%s", minimumSwaggerVersion, runtime.GOOS, runtime.GOARCH) } diff --git a/helpers/dependencies/utils.go b/helpers/dependencies/utils.go index ff1f37c..d791408 100644 --- a/helpers/dependencies/utils.go +++ b/helpers/dependencies/utils.go @@ -20,13 +20,17 @@ import ( // downloadFile will download file from a given url and store in memory the content // content will probably be untar, ungzip, .... func downloadFile(url string) (io.Reader, error) { - // Get the data resp, err := http.Get(url) if err != nil { return nil, errors.Trace(err) } - defer resp.Body.Close() + + defer func() { + if err := resp.Body.Close(); err != nil { + log.Errorf("Error closing body: %s", err) + } + }() buf := new(bytes.Buffer) _, err = buf.ReadFrom(resp.Body) @@ -38,12 +42,16 @@ func downloadFile(url string) (io.Reader, error) { } func unGzip(reader io.Reader) (io.Reader, error) { - gzipReader, err := gzip.NewReader(reader) if err != nil { return nil, errors.Trace(err) } - defer gzipReader.Close() + + defer func() { + if err := gzipReader.Close(); err != nil { + log.Errorf("Error closing gzip reader: %s", err) + } + }() return gzipReader, nil } @@ -59,7 +67,7 @@ func unTar(reader io.Reader, subdir string, dest string) error { header, err := tr.Next() switch { // no more files - case err == io.EOF: + case errors.Is(err, io.EOF): return nil case err != nil: return errors.Trace(err) @@ -70,7 +78,6 @@ func unTar(reader io.Reader, subdir string, dest string) error { filename := header.Name if subdir != "" && strings.HasPrefix(filename, subdir) { - filename = strings.TrimPrefix(filename, subdir) if filename == "" { @@ -78,7 +85,10 @@ func unTar(reader io.Reader, subdir string, dest string) error { } } - target := filepath.Join(dest, filename) + target, err := sanitizeArchivePath(dest, filename) + if err != nil { + return errors.Trace(err) + } log.Debugf("Extacting %s", target) @@ -86,7 +96,7 @@ func unTar(reader io.Reader, subdir string, dest string) error { // create directory if doesn't exit case tar.TypeDir: if _, err := os.Stat(target); err != nil { - if err := os.MkdirAll(target, 0755); err != nil { + if err := os.MkdirAll(target, 0750); err != nil { return errors.Trace(err) } } @@ -96,7 +106,12 @@ func unTar(reader io.Reader, subdir string, dest string) error { if err != nil { return errors.Trace(err) } - defer f.Close() + + defer func() { + if err := f.Close(); err != nil { + log.Errorf("Error closing file: %s", err) + } + }() // copy contents to file if _, err := io.Copy(f, tr); err != nil { @@ -107,7 +122,6 @@ func unTar(reader io.Reader, subdir string, dest string) error { } func unZip(reader io.Reader, subdir string, dest string) error { - if subdir != "" && !strings.HasSuffix(subdir, "/") { subdir = fmt.Sprintf("%s/", subdir) } @@ -128,11 +142,9 @@ func unZip(reader io.Reader, subdir string, dest string) error { } for _, file := range zipReader.File { - filename := file.Name if subdir != "" && strings.HasPrefix(filename, subdir) { - filename = strings.TrimPrefix(filename, subdir) if filename == "" { @@ -140,29 +152,41 @@ func unZip(reader io.Reader, subdir string, dest string) error { } } - target := filepath.Join(dest, filename) + target, err := sanitizeArchivePath(dest, filename) + if err != nil { + return errors.Trace(err) + } log.Debugf("Extacting %s", target) if file.FileInfo().IsDir() { if _, err := os.Stat(target); err != nil { - if err := os.MkdirAll(target, 0755); err != nil { + if err := os.MkdirAll(target, 0750); err != nil { return errors.Trace(err) } } } else { - f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, file.Mode()) if err != nil { return errors.Trace(err) } - defer f.Close() + + defer func() { + if err := f.Close(); err != nil { + log.Errorf("Error closing file: %s", err) + } + }() fileInArchive, err := file.Open() if err != nil { return errors.Trace(err) } - defer fileInArchive.Close() + + defer func() { + if err := fileInArchive.Close(); err != nil { + log.Errorf("Error closing file: %s", err) + } + }() // copy contents to file if _, err := io.Copy(f, fileInArchive); err != nil { @@ -173,3 +197,12 @@ func unZip(reader io.Reader, subdir string, dest string) error { return nil } + +// sanitizeArchivePath will sanitize archive file pathing from "G305: Zip Slip vulnerability". +func sanitizeArchivePath(d string, t string) (string, error) { + if v := filepath.Join(d, t); strings.HasPrefix(v, filepath.Clean(d)) { + return v, nil + } + + return "", fmt.Errorf("%s: %s", "content filepath is tainted", t) +} diff --git a/helpers/file.go b/helpers/file.go index 82e69c1..ab02e14 100644 --- a/helpers/file.go +++ b/helpers/file.go @@ -2,7 +2,6 @@ package helpers import ( "io/fs" - "io/ioutil" "os" "path/filepath" @@ -117,7 +116,6 @@ func CheckAndCreateDir(path string) (bool, error) { err = createDirectory(path) if err != nil { - log.Warnf("impossible to create directory (%s), please try again", err.Error()) return false, err } @@ -127,20 +125,18 @@ func CheckAndCreateDir(path string) (bool, error) { func RemoveDirectoryContent(path string) error { - dir, err := ioutil.ReadDir(path) + dir, err := os.ReadDir(path) if err != nil { return errors.Trace(err) } for _, d := range dir { - err := os.RemoveAll( filepath.Join(path, d.Name()), ) if err != nil { return errors.Trace(err) } - } return nil diff --git a/helpers/input.go b/helpers/input.go index 8cdb624..afda2f8 100644 --- a/helpers/input.go +++ b/helpers/input.go @@ -106,7 +106,7 @@ func PathInput() string { // APITypeNameInput func APITypeNameInput() models.APITypeName { - var possibleAPITypes []string + possibleAPITypes := make([]string, 0) for _, apiType := range models.GetListOfAPITypeName() { possibleAPITypes = append(possibleAPITypes, string(apiType)) } diff --git a/helpers/models/api_type.go b/helpers/models/api_type.go index 2bd22f2..157705d 100644 --- a/helpers/models/api_type.go +++ b/helpers/models/api_type.go @@ -1,6 +1,6 @@ package models -// UserInputParams +// UserInputParams is a struct containing all fields that can be useful for project generation. type UserInputParams struct { DB bool GoModuleName *string diff --git a/helpers/models/api_type_name_enum.go b/helpers/models/api_type_name_enum.go index 4ba5a1a..b80cf10 100644 --- a/helpers/models/api_type_name_enum.go +++ b/helpers/models/api_type_name_enum.go @@ -6,9 +6,10 @@ import ( "github.com/juju/errors" ) -// APITypeName +// APITypeName is a type used as an enum for possible API Type Name. type APITypeName string +//nolint:exported // keeping the enum simple and redeable. const ( APITypeName_GIN_GONIC APITypeName = "Gin Gonic" APITypeName_GO_SWAGGER APITypeName = "Go Swagger" @@ -16,7 +17,7 @@ const ( APITypeName_NULL APITypeName = "" ) -// GetListOfAPITypeName returns a list of APITypeName +// GetListOfAPITypeName returns a list of APITypeName. func GetListOfAPITypeName() []APITypeName { return []APITypeName{ APITypeName_GIN_GONIC, @@ -25,7 +26,7 @@ func GetListOfAPITypeName() []APITypeName { } } -// IsValid validates enum values +// IsValid validates enum values. func (e APITypeName) IsValid() bool { for _, v := range GetListOfAPITypeName() { if e == v { @@ -36,6 +37,7 @@ func (e APITypeName) IsValid() bool { return false } +// NewAPITypeName return a APITypeName based on a given string. func NewAPITypeName(in string) (APITypeName, error) { out := APITypeName_NULL if in != "" { @@ -47,9 +49,8 @@ func NewAPITypeName(in string) (APITypeName, error) { return out, nil } -// NewAPITypeNameFromInput +// NewAPITypeNameFromInput will try to sluggify a given string and make it match with a APITypeName. func NewAPITypeNameFromInput(in string) (APITypeName, error) { - in = strings.ToLower(in) in = strings.ReplaceAll(in, " ", "-") in = strings.ReplaceAll(in, "_", "-") diff --git a/helpers/models/dependency_name_enum.go b/helpers/models/dependency_name_enum.go index cae3f46..5696d00 100644 --- a/helpers/models/dependency_name_enum.go +++ b/helpers/models/dependency_name_enum.go @@ -1,8 +1,9 @@ package models -// DependencyName +// DependencyName is a type used as an enum for possible Dependency Name. type DependencyName string +//nolint:exported // keeping the enum simple and redeable. const ( DependencyName_GIT DependencyName = "Git" DependencyName_GOLANG DependencyName = "Golang"