gouick/helpers/dependencies/golang.go

342 lines
8.4 KiB
Go
Raw Normal View History

package dependencies
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/blang/semver"
"github.com/juju/errors"
2022-07-28 22:03:57 +00:00
"git.home.m-and-m.ovh/mderasse/gouick/helpers"
"git.home.m-and-m.ovh/mderasse/gouick/helpers/models"
log "github.com/sirupsen/logrus"
)
// 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.
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.
// GetName return the name of the dependency using DependencyName enum.
func (g Golang) GetName() models.DependencyName {
return models.DependencyName_GOLANG
}
// GetMinimumVersion return the minimum version required for that dependency.
func (g Golang) GetMinimumVersion() string {
return minimumGolangVersion
}
// 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)
} else if err != nil && errors.Is(err, exec.ErrNotFound) {
return false, nil
}
return true, nil
}
// GetBinaryPath will search for the binary and return the path if found.
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 will find the current version of the dependency.
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")
}
binaryPath, err := g.GetBinaryPath()
if err != nil {
return "", errors.Trace(err)
}
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 {
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 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)
}
if !isInstalled {
return false, nil
}
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
}
// CanBeInstalled define if the dependency installation is handled by the app.
func (g Golang) CanBeInstalled() bool {
return true
}
// 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),
fmt.Sprintf("curl %s | tar --strip-components=1 -C %s -zxf -", g.getDownloadURL(), path),
}
return strings.Join(commands, "\n")
}
// 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)
}
downloadURL := g.getDownloadURL()
content, err := downloadFile(downloadURL)
if err != nil {
log.Warnf("fail to download file from %s", downloadURL)
return errors.Trace(err)
}
// zip on windows, tar gz on other platform
if runtime.GOOS == "windows" {
log.Debug("Working on zip, Unzip")
err = unZip(content, "go/", path)
if err != nil {
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)
if err != nil {
log.Warnf("fail to un-gzip downloaded file from %s, error:", downloadURL)
return errors.Trace(err)
}
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
}
// 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:
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 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
}
lineBashRc := []string{
2022-07-25 20:43:53 +00:00
"# Golang - Added by gouick",
}
createGoPath := false
if os.Getenv("GOPATH") == "" {
lineBashRc = append(lineBashRc, "export GOPATH=\"$HOME/go\"")
createGoPath = true
}
if os.Getenv("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")
//nolint:gosec // we did compute the file path
fh, err := os.OpenFile(
filepath.Join(homeDir, ".bashrc"),
os.O_APPEND|os.O_CREATE|os.O_WRONLY,
0600,
)
if err != nil {
return errors.Trace(err)
}
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")),
)
if err != nil {
return errors.Trace(err)
}
}
if createGoPath {
log.Debug("creating gopath directory")
_, err = helpers.CheckAndCreateDir(filepath.Join(homeDir, "go"))
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".
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 filepath.Join(homeDir, defaultGolangInstallDir), nil
}
// now let's play and find the current install path
binaryPath, err := g.GetBinaryPath()
if err != nil {
return "", errors.Trace(err)
}
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 {
return "", errors.Trace(err)
}
cleanOut := strings.TrimSpace(string(stdout))
if !strings.Contains(cleanOut, homeDir) {
return filepath.Join(homeDir, defaultGolangInstallDir), nil
}
return cleanOut, nil
}
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)
}
return fmt.Sprintf("https://dl.google.com/go/go%s.%s-%s.tar.gz", minimumGolangVersion, runtime.GOOS, runtime.GOARCH)
}