commit f04f5513ab6f0282de7c2efe1d56eb45504df69b Author: Matthieu 'JP' DERASSE Date: Fri Jul 15 11:49:53 2022 +0000 feat(dependencies): Implement base of the dependency system diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/cmd/generate.go b/cmd/generate.go new file mode 100644 index 0000000..8dbbc5f --- /dev/null +++ b/cmd/generate.go @@ -0,0 +1,36 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "fmt" + + "git.home.m-and-m.ovh/mderasse/boot/helpers" + "github.com/spf13/cobra" +) + +// 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) { + + if isStrap, err := helpers.IsStrapProject(); err != nil { + + fmt.Print("Fail to check if we are in a Strap Directory") + + } else if !isStrap { + fmt.Printf("You are not in a Strap Project directory\n") + return + } + fmt.Println("generate called") + }, +} + +func init() { + rootCmd.AddCommand(generateCmd) +} diff --git a/cmd/generateAPI.go b/cmd/generateAPI.go new file mode 100644 index 0000000..9d8d2d2 --- /dev/null +++ b/cmd/generateAPI.go @@ -0,0 +1,24 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// apiCmd represents the api command +var apiCmd = &cobra.Command{ + Use: "api", + Short: "Generate Go-Swagger Files and Controllers", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("api called") + }, +} + +func init() { + generateCmd.AddCommand(apiCmd) +} diff --git a/cmd/generateDB.go b/cmd/generateDB.go new file mode 100644 index 0000000..7bf1a00 --- /dev/null +++ b/cmd/generateDB.go @@ -0,0 +1,24 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// dbCmd represents the db command +var dbCmd = &cobra.Command{ + Use: "db", + Short: "Generate Database Struct from pkg/dbmodels/schema.yaml", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("db called") + }, +} + +func init() { + generateCmd.AddCommand(dbCmd) +} diff --git a/cmd/generateDocker.go b/cmd/generateDocker.go new file mode 100644 index 0000000..14cffd1 --- /dev/null +++ b/cmd/generateDocker.go @@ -0,0 +1,24 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// dockerCmd represents the docker command +var dockerCmd = &cobra.Command{ + Use: "docker", + Short: "Generate Dockerfile for API & Workers", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("docker called") + }, +} + +func init() { + generateCmd.AddCommand(dockerCmd) +} diff --git a/cmd/generateHelm.go b/cmd/generateHelm.go new file mode 100644 index 0000000..4586c57 --- /dev/null +++ b/cmd/generateHelm.go @@ -0,0 +1,24 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// helmCmd represents the helm command +var helmCmd = &cobra.Command{ + Use: "helm", + Short: "Generate Kubernetes Helm chart for API & Workers", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("helm called") + }, +} + +func init() { + generateCmd.AddCommand(helmCmd) +} diff --git a/cmd/generateHelp.go b/cmd/generateHelp.go new file mode 100644 index 0000000..75b7933 --- /dev/null +++ b/cmd/generateHelp.go @@ -0,0 +1,25 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// helpCmd represents the help command +var helpCmd = &cobra.Command{ + Use: "help", + Short: "Show that help", + Run: func(cmd *cobra.Command, args []string) { + err := generateCmd.Help() + if err != nil { + return + } + }, +} + +func init() { + generateCmd.AddCommand(helpCmd) +} diff --git a/cmd/generateLauncher.go b/cmd/generateLauncher.go new file mode 100644 index 0000000..98db40c --- /dev/null +++ b/cmd/generateLauncher.go @@ -0,0 +1,24 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// launcherCmd represents the launcher command +var launcherCmd = &cobra.Command{ + Use: "launcher", + Short: "Generate Launcher Bash script", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("launcher called") + }, +} + +func init() { + generateCmd.AddCommand(launcherCmd) +} diff --git a/cmd/generateMakefile.go b/cmd/generateMakefile.go new file mode 100644 index 0000000..39ed8fb --- /dev/null +++ b/cmd/generateMakefile.go @@ -0,0 +1,24 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// makefileCmd represents the makefile command +var makefileCmd = &cobra.Command{ + Use: "makefile", + Short: "Generate Makefile", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("makefile called") + }, +} + +func init() { + generateCmd.AddCommand(makefileCmd) +} diff --git a/cmd/init.go b/cmd/init.go new file mode 100644 index 0000000..059a543 --- /dev/null +++ b/cmd/init.go @@ -0,0 +1,61 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "github.com/spf13/cobra" + + log "github.com/sirupsen/logrus" +) + +// initCmd represents the init command +var initCmd = &cobra.Command{ + Use: "init", + Short: "Initialize a new project", + Long: `Initialize a new project by creating a new directory and: + - Initialize a Git Repository + - Create a Go-Swagger Struct + - Add default routes + - Generate the API. + +That command is Interactive or can be control through args`, + Run: runInitAction, +} + +func init() { + rootCmd.AddCommand(initCmd) +} + +func runInitAction(cmd *cobra.Command, args []string) { + + log.Debugf("Starting command Init") + + log.Debugf("Checking dependecies") + + // isDependeciesOk, err := helpers.CheckDependencies() + // if err != nil { + // log.Error(err.Error()) + // return + // } else if isDependeciesOk { + // log.Error("Dependencies are not repescted") + // return + // } + + // if isGoProject, err := helpers.IsGoProject(); err != nil { + // log.Error("An error occured when checking your directory.") + // return + // } else if isGoProject { + // log.Error("Cannot initialize a new project because you are already in a Golang Project Directory.") + // return + // } + + // if isStrapProject, err := helpers.IsStrapProject(); err != nil { + // log.Error("An error occured when checking your directory.") + // return + // } else if isStrapProject { + // log.Error("Cannot initialize a new project because you are already in a Strap Project Directory.") + // return + // } +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..fcbe7a6 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,51 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" + + log "github.com/sirupsen/logrus" +) + +var verbose = false +var acceptAll = false + +// 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", + Long: `Boot is a toolbox app to bootstrap quickly a Go-Swagger API. + +It will: + - Auto-Update + - Download dependencies (go-swagger, ...) + - Initialize an API with default middlewares + - Generate Models / Controlers + - Generate Database structs + - ....`, + + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if verbose { + log.SetLevel(log.DebugLevel) + } + }, +} + +// 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 { + os.Exit(1) + } +} + +func init() { + rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "enable verbose logging") + rootCmd.PersistentFlags().BoolVarP(&acceptAll, "yes", "y", false, "accept all change, disable interactive process") +} diff --git a/cmd/test.go b/cmd/test.go new file mode 100644 index 0000000..4ddc231 --- /dev/null +++ b/cmd/test.go @@ -0,0 +1,24 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// testCmd represents the test command +var testCmd = &cobra.Command{ + Use: "test", + Short: "Launch Test", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("test called") + }, +} + +func init() { + rootCmd.AddCommand(testCmd) +} diff --git a/cmd/upgrade.go b/cmd/upgrade.go new file mode 100644 index 0000000..a34258e --- /dev/null +++ b/cmd/upgrade.go @@ -0,0 +1,116 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "github.com/spf13/cobra" + + "git.home.m-and-m.ovh/mderasse/boot/helpers" + "git.home.m-and-m.ovh/mderasse/boot/helpers/dependencies" + + log "github.com/sirupsen/logrus" +) + +// upgradeCmd represents the upgrade command +var upgradeCmd = &cobra.Command{ + Use: "upgrade", + Short: "Upgrade all development dependencies", + Long: `Upgrade the following dependencies: + - Golang + - Go Swagger + - ... +`, + Run: runUpgradeAction, +} + +func init() { + rootCmd.AddCommand(upgradeCmd) +} + +func runUpgradeAction(cmd *cobra.Command, args []string) { + + dependencies := []dependencies.Dependency{dependencies.Golang{}} + + for _, dependency := range dependencies { + + log.Debugf("Checking if dependency %s is supported", dependency.GetName()) + + isSupported, err := dependency.IsVersionSupported() + if err != nil { + log.Errorf("Fail to check %s version. The following error happen: %s", dependency.GetName(), err.Error()) + return + } + + if isSupported { + log.Infof("%s version is supported, continue", dependency.GetName()) + continue + } + + log.Infof("Your %s is not supported.", dependency.GetName()) + + if !acceptAll { + log.Infof("Do you want to install the following version: %s", dependency.GetMinimumVersion()) + + answer := helpers.YesOrNoInput() + if !answer { + log.Warnf("Skipping installation of %s. Some part of the application might not be able to work correctly!", dependency.GetName()) + continue + } + } + + log.Debug("Trying to find the best installation directory") + + installDirectory, err := dependency.GetInstallDirectory() + if err != nil { + log.Errorf("Fail to compute the %s install directory. The following error happen: %s", dependency.GetName(), err.Error()) + return + } + + // ask user if the path is correct, else ask new path and check validity + if !acceptAll { + log.Infof("Do you want to install %s in the directory %s ?", dependency.GetName(), installDirectory) + + answer := helpers.YesOrNoInput() + if !answer { + log.Infof("Where do you want to install %s ?", dependency.GetName()) + installDirectory = helpers.IsValidPathInput() + } + + } else { + log.Infof("Installing %s in the following directory: %s", dependency.GetName(), installDirectory) + } + + log.Debug("Checking directory and creating it if needed") + + // 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()) + if acceptAll { + return + } + log.Infof("Where do you want to install %s ?", dependency.GetName()) + installDirectory = helpers.IsValidPathInput() + } + + log.Infof("The following command will be executed:\n%s", dependency.DescribeInstall(installDirectory)) + log.Info("Do you want to continue ?") + answer := helpers.YesOrNoInput() + if !answer { + log.Warnf("Skipping installation of %s. Some part of the application might not be able to work correctly!", dependency.GetName()) + continue + } + + log.Debug("Starting the installation") + + err = dependency.Install(installDirectory) + if err != nil { + log.Errorf("Fail to install %s version. The following error happen: %s", dependency.GetName(), err.Error()) + return + } + + log.Infof("%s successfully installed", dependency.GetName()) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..38735fa --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module git.home.m-and-m.ovh/mderasse/boot + +go 1.17 + +require ( + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/juju/errors v0.0.0-20220331221717-b38fca44723b // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spf13/cobra v1.4.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b5cddce --- /dev/null +++ b/go.sum @@ -0,0 +1,21 @@ +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/juju/errors v0.0.0-20220331221717-b38fca44723b h1:AxFeSQJfcm2O3ov1wqAkTKYFsnMw2g1B4PkYujfAdkY= +github.com/juju/errors v0.0.0-20220331221717-b38fca44723b/go.mod h1:jMGj9DWF/qbo91ODcfJq6z/RYc3FX3taCBZMCcpI4Ls= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/helpers/constant.go b/helpers/constant.go new file mode 100644 index 0000000..54a18b0 --- /dev/null +++ b/helpers/constant.go @@ -0,0 +1,3 @@ +package helpers + +const configFile = ".strap.yaml" diff --git a/helpers/dependencies/golang.go b/helpers/dependencies/golang.go new file mode 100644 index 0000000..c0b2dd8 --- /dev/null +++ b/helpers/dependencies/golang.go @@ -0,0 +1,214 @@ +package dependencies + +import ( + "fmt" + "os" + "os/exec" + "regexp" + "runtime" + "strings" + + "github.com/blang/semver" + "github.com/juju/errors" + + "git.home.m-and-m.ovh/mderasse/boot/helpers" + + log "github.com/sirupsen/logrus" +) + +// minimum minor required for the app to work +const minimumGolangVersion = "1.18.4" + +// installation directory for fresh install. +// will be prefixed by $HOME +const defaultGolangInstallDir = "/local/go" + +var regexGolangVersion = regexp.MustCompile(`^go version go(\d+\.\d+\.\d+)`) + +type Golang struct{} + +// regroup all golang dependencies function + +// GetName +func (g Golang) GetName() string { + return "Golang" +} + +// GetMinimumVersion +func (g Golang) GetMinimumVersion() string { + return minimumGolangVersion +} + +// IsInstalled +func (g Golang) IsInstalled() (bool, error) { + + _, err := g.GetBinaryPath() + if err != nil && !errors.Is(err, exec.ErrNotFound) { + return false, errors.Trace(err) + } else if err != nil && errors.Is(err, exec.ErrNotFound) { + return false, nil + } + + return true, nil +} + +// GetBinaryPath +func (g Golang) GetBinaryPath() (string, error) { + log.Debug("looking for golang binary") + + path, err := exec.LookPath("go") + if err != nil { + return "", errors.Trace(err) + } + + log.Debug("found golang binary in", path) + + return path, nil +} + +// GetVersion return the major, minor and patch version of Golang +func (g Golang) GetVersion() (string, error) { + + isInstalled, err := g.IsInstalled() + if err != nil { + return "", errors.Trace(err) + } + + if !isInstalled { + return "", errors.NotFoundf("golang is not installed on the system") + } + + golangPath, err := g.GetBinaryPath() + if err != nil { + return "", errors.Trace(err) + } + + log.Debug("executing go version command") + + cmd := exec.Command(golangPath, "version") + stdout, err := cmd.Output() + if err != nil { + return "", errors.Trace(err) + } + cleanOut := strings.TrimSpace(string(stdout)) + + log.Debugf("go version returned %s", cleanOut) + + parseOutput := regexGolangVersion.FindStringSubmatch(cleanOut) + if len(parseOutput) != 2 { + return "", errors.NotSupportedf("failed to parse golang version output: %s", cleanOut) + } + + return parseOutput[1], nil +} + +// IsVersionSupported +func (g Golang) IsVersionSupported() (bool, error) { + + version, err := g.GetVersion() + if err != nil { + return false, errors.Trace(err) + } + + installedVersion, err := semver.Make(version) + if err != nil { + return false, errors.Trace(err) + } + + requiredVersion, _ := semver.Make(minimumGolangVersion) + if err != nil { + return false, errors.Trace(err) + } + + if installedVersion.LT(*&requiredVersion) { + return false, nil + } + + return true, nil +} + +// DescribeInstall +func (g Golang) DescribeInstall(path string) string { + + description := fmt.Sprintf("rm -rf %s/* ", path) + description = fmt.Sprintf("%s\ncurl %s | tar --strip-components=1 -C %s -zxf -", description, getDownloadUrl(), path) + return description +} + +// Install +func (g Golang) Install(path string) error { + + err := helpers.RemoveDirectoryContent(path) + if err != nil { + log.Warnf("fail to delete content of directory %s", path) + } + + downloadUrl := getDownloadUrl() + content, err := downloadFile(downloadUrl) + if err != nil { + log.Warnf("fail to download file from %s", downloadUrl) + return errors.Trace(err) + } + + gzipReader, err := unGzip(content) + if err != nil { + log.Warnf("fail to un-gzip downloaded file from %s, error:", downloadUrl) + return errors.Trace(err) + } + + // XXX: unTar should take a subdir + err = unTar(gzipReader, "go/", path) + if err != nil { + log.Warnf("fail to un-tar downloaded file from %s", downloadUrl) + return errors.Trace(err) + } + + return nil +} + +// 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" +func (g Golang) GetInstallDirectory() (string, error) { + + homeDir, err := os.UserHomeDir() + if err != nil { + return "", errors.Trace(err) + } + + isInstalled, err := g.IsInstalled() + if err != nil { + return "", errors.Trace(err) + } + + // concat default install dir with home and we have our path + if !isInstalled { + return fmt.Sprintf("%s/%s", homeDir, defaultGolangInstallDir), nil + } + + // now let's play and find the current install path + golangPath, err := g.GetBinaryPath() + if err != nil { + return "", errors.Trace(err) + } + + log.Debug("executing go env GOROOT command") + + cmd := exec.Command(golangPath, "env", "GOROOT") + stdout, err := cmd.Output() + if err != nil { + return "", errors.Trace(err) + } + cleanOut := strings.TrimSpace(string(stdout)) + + if !strings.Contains(cleanOut, homeDir) { + return fmt.Sprintf("%s/%s", homeDir, defaultGolangInstallDir), nil + } + + return cleanOut, nil +} + +func getDownloadUrl() string { + + 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 new file mode 100644 index 0000000..da57ed9 --- /dev/null +++ b/helpers/dependencies/interface.go @@ -0,0 +1,14 @@ +package dependencies + +// Dependency +type Dependency interface { + DescribeInstall(path string) string + GetBinaryPath() (string, error) + GetInstallDirectory() (string, error) + GetName() string + GetMinimumVersion() string + GetVersion() (string, error) + Install(path string) error + IsInstalled() (bool, error) + IsVersionSupported() (bool, error) +} diff --git a/helpers/dependencies/utils.go b/helpers/dependencies/utils.go new file mode 100644 index 0000000..5671b67 --- /dev/null +++ b/helpers/dependencies/utils.go @@ -0,0 +1,106 @@ +package dependencies + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/juju/errors" + + log "github.com/sirupsen/logrus" +) + +// 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() + + buf := new(bytes.Buffer) + _, err = buf.ReadFrom(resp.Body) + if err != nil { + return nil, errors.Trace(err) + } + + return buf, nil +} + +func unGzip(reader io.Reader) (io.Reader, error) { + + gzipReader, err := gzip.NewReader(reader) + if err != nil { + return nil, errors.Trace(err) + } + defer gzipReader.Close() + + return gzipReader, nil +} + +func unTar(reader io.Reader, subdir string, dest string) error { + tr := tar.NewReader(reader) + + if subdir != "" && !strings.HasSuffix(subdir, "/") { + subdir = fmt.Sprintf("%s/", subdir) + } + + for { + header, err := tr.Next() + switch { + // no more files + case err == io.EOF: + return nil + case err != nil: + return errors.Trace(err) + case header == nil: + continue + } + + filename := header.Name + + if subdir != "" && strings.HasPrefix(filename, subdir) { + + filename = strings.Replace(filename, subdir, "", 1) + + if filename == "" { + continue + } + } + + target := filepath.Join(dest, filename) + + log.Debugf("Extacting %s", target) + + switch header.Typeflag { + // create directory if doesn't exit + case tar.TypeDir: + if _, err := os.Stat(target); err != nil { + if err := os.MkdirAll(target, 0755); err != nil { + return errors.Trace(err) + } + } + // create file + case tar.TypeReg: + f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + if err != nil { + return errors.Trace(err) + } + defer f.Close() + + // copy contents to file + if _, err := io.Copy(f, tr); err != nil { + return errors.Trace(err) + } + } + } +} diff --git a/helpers/file.go b/helpers/file.go new file mode 100644 index 0000000..8d5f374 --- /dev/null +++ b/helpers/file.go @@ -0,0 +1,140 @@ +package helpers + +import ( + "fmt" + "io/fs" + "io/ioutil" + "os" + "path/filepath" + + "github.com/juju/errors" + + log "github.com/sirupsen/logrus" +) + +func IsStrapProject() (bool, error) { + + currentPath, err := os.Getwd() + if err != nil { + return false, err + } + + if !fileExists(fmt.Sprintf("%s/%s", currentPath, configFile)) { + return false, nil + } + + return true, nil +} + +func IsGoProject() (bool, error) { + + currentPath, err := os.Getwd() + if err != nil { + return false, err + } + + if fileExists(fmt.Sprintf("%s/%s", currentPath, "go.mod")) { + return true, nil + } + + if fileExists(fmt.Sprintf("%s/%s", currentPath, "go.sum")) { + return true, nil + } + + return false, nil +} + +func fileExists(filename string) bool { + info, err := os.Stat(filename) + + if errors.Is(err, fs.ErrNotExist) { + return false + } + return !info.IsDir() +} + +// isDirectoryWritable +func isDirectoryWritable(path string) (bool, error) { + + dirInfo, err := os.Stat(path) + if err != nil { + + if errors.Is(err, fs.ErrPermission) { + return false, errors.NewForbidden(err, "directory is not readeable") + } else if errors.Is(err, fs.ErrNotExist) { + return false, errors.NewNotFound(err, "directory does not exit") + } else { + return false, errors.Trace(err) + } + } + + if !dirInfo.IsDir() { + return false, errors.NotSupportedf("given path is not a directory") + } + + if dirInfo.Mode().Perm()&(1<<(uint(7))) == 0 { + return false, nil + } + + return true, nil +} + +// createDirectory +func createDirectory(path string) error { + + // no need to check if path exist + // MkdirAll will do it for us + err := os.MkdirAll(path, os.ModePerm) + if err != nil { + return errors.Trace(err) + } + + return nil +} + +// CheckAndCreateDir will check if path is writable and create directory if needed +func CheckAndCreateDir(path string) error { + + dirWritable, err := isDirectoryWritable(path) + if dirWritable { + return nil + } else if err != nil && !errors.Is(err, errors.NotFound) { + log.Warnf("impossible to check if the directory is writable") + return err + } else if err == nil && !dirWritable { + log.Warnf("directory is not writable") + return errors.Forbiddenf("directory is not writable") + } + + err = createDirectory(path) + if err != nil { + + log.Warnf("impossible to create directory (%s), please try again", err.Error()) + return err + } + + return nil +} + +func RemoveDirectoryContent(path string) error { + + dir, err := ioutil.ReadDir(path) + if err != nil { + return errors.Trace(err) + } + + for _, d := range dir { + + err := os.RemoveAll( + filepath.Join( + []string{"path", d.Name()}..., + ), + ) + if err != nil { + return errors.Trace(err) + } + + } + + return nil +} diff --git a/helpers/http.go b/helpers/http.go new file mode 100644 index 0000000..345b806 --- /dev/null +++ b/helpers/http.go @@ -0,0 +1 @@ +package helpers diff --git a/helpers/input.go b/helpers/input.go new file mode 100644 index 0000000..311d306 --- /dev/null +++ b/helpers/input.go @@ -0,0 +1,58 @@ +package helpers + +import ( + "fmt" + "strings" + + log "github.com/sirupsen/logrus" +) + +// YesOrNoInput +func YesOrNoInput() bool { + + var userInput string + for { + + _, err := fmt.Scanf("%s", &userInput) + if err != nil { + log.Infof("failed to read input, try again (%s)", err.Error()) + continue + } + + lUserInput := strings.ToLower(userInput) + + for _, positiveAnswer := range []string{"yes", "y", "1", "true"} { + if lUserInput == positiveAnswer { + return true + } + } + for _, negativeAnswer := range []string{"no", "n", "0", "false"} { + if lUserInput == negativeAnswer { + return false + } + } + + log.Info("Expecting a yes or no answer, Try again") + } +} + +// IsValidPathInput +func IsValidPathInput() string { + var userInput string + for { + + _, err := fmt.Scanf("%s", &userInput) + if err != nil { + log.Infof("failed to read input, try again (%s)", err.Error()) + continue + } + + err = CheckAndCreateDir(userInput) + if err != nil { + log.Warnf("please, try again") + continue + } + + return userInput + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..5005149 --- /dev/null +++ b/main.go @@ -0,0 +1,11 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package main + +import "git.home.m-and-m.ovh/mderasse/boot/cmd" + +func main() { + cmd.Execute() +}