From 7c9cc568abacb1b520068d0f15f0d1c91ee07b77 Mon Sep 17 00:00:00 2001 From: Matthieu 'JP' DERASSE Date: Fri, 15 Jul 2022 22:16:59 +0000 Subject: [PATCH] feat(dependencies): Add postinstall process and swagger dependency --- README.md | 1 + cmd/upgrade.go | 22 ++- go.mod | 17 +- go.sum | 7 + helpers/dependencies/golang.go | 125 ++++++++++++-- helpers/dependencies/interface.go | 2 + helpers/dependencies/swagger.go | 276 ++++++++++++++++++++++++++++++ helpers/dependencies/utils.go | 2 +- 8 files changed, 430 insertions(+), 22 deletions(-) create mode 100644 README.md create mode 100644 helpers/dependencies/swagger.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..87532bc --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Goguik (name TBD) \ No newline at end of file diff --git a/cmd/upgrade.go b/cmd/upgrade.go index a34258e..2d715fd 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -31,7 +31,10 @@ func init() { func runUpgradeAction(cmd *cobra.Command, args []string) { - dependencies := []dependencies.Dependency{dependencies.Golang{}} + dependencies := []dependencies.Dependency{ + dependencies.Golang{}, + dependencies.Swagger{}, + } for _, dependency := range dependencies { @@ -95,7 +98,7 @@ func runUpgradeAction(cmd *cobra.Command, args []string) { installDirectory = helpers.IsValidPathInput() } - log.Infof("The following command will be executed:\n%s", dependency.DescribeInstall(installDirectory)) + log.Infof("%s", dependency.DescribeInstall(installDirectory)) log.Info("Do you want to continue ?") answer := helpers.YesOrNoInput() if !answer { @@ -111,6 +114,21 @@ func runUpgradeAction(cmd *cobra.Command, args []string) { return } + log.Infof("%s", dependency.DescribePostInstall(installDirectory)) + log.Info("Do you want to continue ?") + answer = helpers.YesOrNoInput() + if !answer { + log.Warnf("Skipping post installation of %s. Some part of the application might not be able to work correctly!", dependency.GetName()) + continue + } + + log.Debug("Launching post install") + err = dependency.PostInstall(installDirectory) + if err != nil { + log.Errorf("Fail to execute post install of %s. 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 index 38735fa..4e5bd25 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,14 @@ 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 + github.com/blang/semver v3.5.1+incompatible + github.com/juju/errors v1.0.0 + github.com/sirupsen/logrus v1.8.1 + github.com/spf13/cobra v1.5.0 +) + +require ( + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect ) diff --git a/go.sum b/go.sum index b5cddce..f4fa194 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,28 @@ 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/cpuguy83/go-md2man/v2 v2.0.2/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/juju/errors v1.0.0 h1:yiq7kjCLll1BiaRuNY53MGI0+EQ3rF6GB+wvboZDefM= +github.com/juju/errors v1.0.0/go.mod h1:B5x9thDqx0wIMH3+aLIMP9HjItInYWObRovoCFM5Qe8= 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/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= 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= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/dependencies/golang.go b/helpers/dependencies/golang.go index c0b2dd8..beb56d9 100644 --- a/helpers/dependencies/golang.go +++ b/helpers/dependencies/golang.go @@ -66,7 +66,7 @@ func (g Golang) GetBinaryPath() (string, error) { return path, nil } -// GetVersion return the major, minor and patch version of Golang +// GetVersion func (g Golang) GetVersion() (string, error) { isInstalled, err := g.IsInstalled() @@ -78,14 +78,14 @@ func (g Golang) GetVersion() (string, error) { return "", errors.NotFoundf("golang is not installed on the system") } - golangPath, err := g.GetBinaryPath() + binaryPath, err := g.GetBinaryPath() if err != nil { return "", errors.Trace(err) } log.Debug("executing go version command") - cmd := exec.Command(golangPath, "version") + cmd := exec.Command(binaryPath, "version") stdout, err := cmd.Output() if err != nil { return "", errors.Trace(err) @@ -105,6 +105,14 @@ func (g Golang) GetVersion() (string, error) { // IsVersionSupported func (g Golang) IsVersionSupported() (bool, error) { + isInstalled, err := g.IsInstalled() + if err != nil { + return false, errors.Trace(err) + } + if !isInstalled { + return false, nil + } + version, err := g.GetVersion() if err != nil { return false, errors.Trace(err) @@ -130,9 +138,13 @@ func (g Golang) IsVersionSupported() (bool, error) { // 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 + commands := []string{ + "The following commands will be executed", + fmt.Sprintf("rm -rf %s/* ", path), + fmt.Sprintf("curl %s | tar --strip-components=1 -C %s -zxf -", g.getDownloadUrl(), path), + } + + return strings.Join(commands, "\n") } // Install @@ -143,7 +155,7 @@ func (g Golang) Install(path string) error { log.Warnf("fail to delete content of directory %s", path) } - downloadUrl := getDownloadUrl() + downloadUrl := g.getDownloadUrl() content, err := downloadFile(downloadUrl) if err != nil { log.Warnf("fail to download file from %s", downloadUrl) @@ -166,6 +178,95 @@ func (g Golang) Install(path string) error { return nil } +// DescribePostInstall +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: + + export GOPATH=\"$HOME/go\"`, + fmt.Sprintf(" export GOROOT=\"%s\"", path), + ` "PATH=\"$GOROOT/bin:$GOPATH/bin:$PATH\" + +You will have to reopen a new terminal to apply the changes or execute the following command: + + source $HOME/.bashrc + +`, + } + + return strings.Join(descriptions, "\n") +} + +// PostInstall +func (g Golang) PostInstall(path string) error { + + lineBashRc := []string{ + "# Golang - Added by goguik", + } + + gopath := os.Getenv("GOPATH") + createGoPath := false + if gopath == "" { + lineBashRc = append(lineBashRc, "export GOPATH=\"$HOME/go\"") + createGoPath = true + } + + goroot := os.Getenv("GOROOT") + if goroot == "" { + lineBashRc = append(lineBashRc, fmt.Sprintf("export GOROOT=\"%s\"", path)) + } + + // checking if go binary is found after installation + // if not it's probably not in the path + isInstalled, err := g.IsInstalled() + if err != nil { + return errors.Trace(err) + } + + if !isInstalled { + lineBashRc = append(lineBashRc, "PATH=\"$GOROOT/bin:$GOPATH/bin:$PATH\"") + } + + homeDir, err := os.UserHomeDir() + if err != nil { + return errors.Trace(err) + } + + if len(lineBashRc) > 1 { + log.Debug("Adding env variable to .bashrc") + + fh, err := os.OpenFile( + fmt.Sprintf("%s.bashrc", homeDir), + os.O_APPEND|os.O_CREATE|os.O_WRONLY, + 0644, + ) + if err != nil { + return errors.Trace(err) + } + defer fh.Close() + + _, err = fh.WriteString( + fmt.Sprintf("\n\n%s\n", strings.Join(lineBashRc, "\n")), + ) + if err != nil { + return errors.Trace(err) + } + } + + if createGoPath { + + log.Debug("creating gopath directory") + + err = helpers.CheckAndCreateDir(fmt.Sprintf("%sgo", homeDir)) + if err != nil { + 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" @@ -183,18 +284,18 @@ func (g Golang) GetInstallDirectory() (string, error) { // concat default install dir with home and we have our path if !isInstalled { - return fmt.Sprintf("%s/%s", homeDir, defaultGolangInstallDir), nil + return fmt.Sprintf("%s%s", homeDir, defaultGolangInstallDir), nil } // now let's play and find the current install path - golangPath, err := g.GetBinaryPath() + binaryPath, err := g.GetBinaryPath() if err != nil { return "", errors.Trace(err) } log.Debug("executing go env GOROOT command") - cmd := exec.Command(golangPath, "env", "GOROOT") + cmd := exec.Command(binaryPath, "env", "GOROOT") stdout, err := cmd.Output() if err != nil { return "", errors.Trace(err) @@ -202,13 +303,13 @@ func (g Golang) GetInstallDirectory() (string, error) { cleanOut := strings.TrimSpace(string(stdout)) if !strings.Contains(cleanOut, homeDir) { - return fmt.Sprintf("%s/%s", homeDir, defaultGolangInstallDir), nil + return fmt.Sprintf("%s%s", homeDir, defaultGolangInstallDir), nil } return cleanOut, nil } -func getDownloadUrl() string { +func (g Golang) 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 index da57ed9..c43c8cd 100644 --- a/helpers/dependencies/interface.go +++ b/helpers/dependencies/interface.go @@ -3,12 +3,14 @@ package dependencies // Dependency type Dependency interface { DescribeInstall(path string) string + DescribePostInstall(path string) string GetBinaryPath() (string, error) GetInstallDirectory() (string, error) GetName() string GetMinimumVersion() string GetVersion() (string, error) Install(path string) error + PostInstall(path string) error IsInstalled() (bool, error) IsVersionSupported() (bool, error) } diff --git a/helpers/dependencies/swagger.go b/helpers/dependencies/swagger.go new file mode 100644 index 0000000..ff51e51 --- /dev/null +++ b/helpers/dependencies/swagger.go @@ -0,0 +1,276 @@ +package dependencies + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "regexp" + "runtime" + "strings" + + "github.com/blang/semver" + "github.com/juju/errors" + + log "github.com/sirupsen/logrus" +) + +// minimum minor required for the app to work +const minimumSwaggerVersion = "0.29.0" + +// installation directory for fresh install. +// will be prefixed by $HOME +const defaultSwaggerInstallDir = "/bin" + +var regexSwaggerVersion = regexp.MustCompile(`^version: v(\d+\.\d+\.\d+)`) + +type Swagger struct{} + +// regroup all swagger dependencies function + +// GetName +func (s Swagger) GetName() string { + return "Go Swagger" +} + +// GetMinimumVersion +func (s Swagger) GetMinimumVersion() string { + return minimumSwaggerVersion +} + +// IsInstalled +func (s Swagger) IsInstalled() (bool, error) { + + _, err := s.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 (s Swagger) GetBinaryPath() (string, error) { + log.Debug("looking for swagger binary") + + path, err := exec.LookPath("swagger") + if err != nil { + return "", errors.Trace(err) + } + + log.Debug("found swagger binary in", path) + + return path, nil +} + +// GetVersion +func (s Swagger) GetVersion() (string, error) { + + isInstalled, err := s.IsInstalled() + if err != nil { + return "", errors.Trace(err) + } + + if !isInstalled { + return "", errors.NotFoundf("swagger is not installed on the system") + } + + binaryPath, err := s.GetBinaryPath() + if err != nil { + return "", errors.Trace(err) + } + + log.Debug("executing swagger version command") + + cmd := exec.Command(binaryPath, "version") + stdout, err := cmd.Output() + if err != nil { + return "", errors.Trace(err) + } + cleanOut := strings.TrimSpace(string(stdout)) + + log.Debugf("swagger version returned %s", cleanOut) + + parseOutput := regexSwaggerVersion.FindStringSubmatch(cleanOut) + if len(parseOutput) != 2 { + return "", errors.NotSupportedf("failed to parse swagger version output: %s", cleanOut) + } + + return parseOutput[1], nil +} + +// IsVersionSupported +func (s Swagger) IsVersionSupported() (bool, error) { + + isInstalled, err := s.IsInstalled() + if err != nil { + return false, errors.Trace(err) + } + if !isInstalled { + return false, nil + } + + version, err := s.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(minimumSwaggerVersion) + if err != nil { + return false, errors.Trace(err) + } + + if installedVersion.LT(*&requiredVersion) { + return false, nil + } + + return true, nil +} + +// DescribeInstall +func (s Swagger) DescribeInstall(path string) string { + + commands := []string{ + "The following commands will be executed", + fmt.Sprintf("rm -rf %s/swagger ", path), + fmt.Sprintf("wget %s -O %s/swagger", s.getDownloadUrl(), path), + fmt.Sprintf("chmod 644 %s/swagger", path), + } + + return strings.Join(commands, "\n") +} + +// Install +func (s Swagger) Install(path string) error { + + downloadUrl := s.getDownloadUrl() + content, err := downloadFile(downloadUrl) + if err != nil { + log.Warnf("fail to download file from %s", downloadUrl) + return errors.Trace(err) + } + + fh, err := os.OpenFile( + fmt.Sprintf("%s/swagger", path), + os.O_RDWR|os.O_CREATE|os.O_TRUNC, + 0744, + ) + if err != nil { + return errors.Trace(err) + } + defer fh.Close() + + bContent, err := ioutil.ReadAll(content) + if err != nil { + return errors.Trace(err) + } + + _, err = fh.Write(bContent) + if err != nil { + return errors.Trace(err) + } + + return nil +} + +// DescribePostInstall +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\" + +You will have to reopen a new terminal to apply the changes or execute the following command: + + source $HOME/.bashrc + +` +} + +// PostInstall +func (s Swagger) PostInstall(path string) error { + + lineBashRc := []string{ + "# Swagger - Added by goguik", + } + + // checking if swagger binary is found after installation + // if not it's probably not in the path + isInstalled, err := s.IsInstalled() + if err != nil { + return errors.Trace(err) + } + + if !isInstalled { + lineBashRc = append(lineBashRc, "PATH=\"$HOME/bin:$PATH\"") + } + + homeDir, err := os.UserHomeDir() + if err != nil { + return errors.Trace(err) + } + + if len(lineBashRc) > 1 { + log.Debug("Adding env variable to .bashrc") + + fh, err := os.OpenFile( + fmt.Sprintf("%s.bashrc", homeDir), + os.O_APPEND|os.O_CREATE|os.O_WRONLY, + 0644, + ) + if err != nil { + return errors.Trace(err) + } + defer fh.Close() + + _, err = fh.WriteString( + fmt.Sprintf("\n\n%s\n", strings.Join(lineBashRc, "\n")), + ) + if err != nil { + return errors.Trace(err) + } + } + + return nil +} + +// 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" +func (s Swagger) GetInstallDirectory() (string, error) { + + homeDir, err := os.UserHomeDir() + if err != nil { + return "", errors.Trace(err) + } + + isInstalled, err := s.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, defaultSwaggerInstallDir), nil + } + + // now let's play and find the current install path + binaryPath, err := s.GetBinaryPath() + if err != nil { + return "", errors.Trace(err) + } + + return strings.TrimSuffix(binaryPath, "/swagger"), nil +} + +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 5671b67..c511e98 100644 --- a/helpers/dependencies/utils.go +++ b/helpers/dependencies/utils.go @@ -70,7 +70,7 @@ func unTar(reader io.Reader, subdir string, dest string) error { if subdir != "" && strings.HasPrefix(filename, subdir) { - filename = strings.Replace(filename, subdir, "", 1) + filename = strings.TrimPrefix(filename, subdir) if filename == "" { continue