Skip to content

Commit

Permalink
Add check for go version, treat errs as warning
Browse files Browse the repository at this point in the history
Signed-off-by: Tobias Brumhard <tobias.brumhard@mail.schwarz>
  • Loading branch information
brumhard committed Oct 30, 2021
1 parent 6a08254 commit ed68423
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 15 deletions.
79 changes: 79 additions & 0 deletions pkg/exec/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package exec

import (
"bytes"
"errors"
"fmt"
"os/exec"
"strings"
)

var ErrInvalidArgs = errors.New("invalid command args")

var _ Command = (*execCmd)(nil)

type Command interface {
// Run executes the command and returns stdout as string as well as an error if the command failed.
// The error is of type ErrWithStderr and can be type asserted to that to get the exact stderr output.
Run() (string, error)
// Args returns all the command's args including the executable name as the first item.
Args() []string
}

type execCmd struct {
*exec.Cmd
}

func (ec *execCmd) Run() (string, error) {
stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
ec.Stdout, ec.Stderr = stdout, stderr

if err := ec.Cmd.Run(); err != nil {
return "", &ErrWithStderr{
Wrapped: err,
Args: ec.Cmd.Args,
StdErr: stderr.Bytes(),
}
}

return stdout.String(), nil
}

func (ec *execCmd) Args() []string {
return ec.Cmd.Args
}

func NewExecCmd(args []string, opts ...NewExecCmdOption) Command {
cmd := exec.Command(args[0], args[1:]...)
for _, opt := range opts {
opt(cmd)
}

return &execCmd{Cmd: cmd}
}

type NewExecCmdOption func(*exec.Cmd)

func WithTargetDir(targetDir string) NewExecCmdOption {
return func(c *exec.Cmd) {
c.Dir = targetDir
}
}

type ErrWithStderr struct {
Wrapped error
StdErr []byte
Args []string
}

func (e *ErrWithStderr) Error() string {
if len(e.StdErr) > 0 {
return fmt.Sprintf("failed running `%s`, %q: %s", strings.Join(e.Args, " "), e.StdErr, e.Wrapped.Error())
}

return fmt.Sprintf("failed running `%s`, make sure %s is available: %s", strings.Join(e.Args, " "), e.Args[0], e.Wrapped.Error())
}

func (e *ErrWithStderr) Unwrap() error {
return e.Wrapped
}
43 changes: 43 additions & 0 deletions pkg/exec/command_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package exec

import (
"fmt"
"strings"

"github.com/pkg/errors"
)

// CommandGroup contains commands that are run one after another.
// As soon as one command fails the whole CommandGroup will be stopped and all other
// not yet executed commands are skipped.
type CommandGroup struct {
// func run before any of the commands is executed
PreRun func() error
// Commands to run
Commands []Command
}

func (cg *CommandGroup) Run() error {
if len(cg.Commands) == 0 {
return nil
}

if cg.PreRun != nil {
if err := cg.PreRun(); err != nil {
var skipsCmds []string
for _, cmd := range cg.Commands {
skipsCmds = append(skipsCmds, fmt.Sprintf("`%s`", strings.Join(cmd.Args(), " ")))
}

return errors.Wrapf(err, "skipping %s", strings.Join(skipsCmds, ", "))
}
}

for _, cmd := range cg.Commands {
if _, err := cmd.Run(); err != nil {
return err
}
}

return nil
}
37 changes: 37 additions & 0 deletions pkg/gocli/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package gocli

import (
"bytes"
"os/exec"
"strings"

"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
)

var ErrMalformedGoVersionOutput = errors.New("malformed go version output")

func Semver() (*semver.Version, error) {
stdout := &bytes.Buffer{}

goVersion := exec.Command("go", "version")
goVersion.Stdout = stdout

err := goVersion.Run()
if err != nil {
return nil, errors.Wrap(err, "failed checking go version")
}

versionParts := strings.Split(stdout.String(), " ")
if len(versionParts) != 4 {
return nil, errors.Wrap(ErrMalformedGoVersionOutput, stdout.String())
}

goSemverString := strings.TrimPrefix(versionParts[2], "go")
goSemver, err := semver.NewVersion(goSemverString)
if err != nil {
return nil, errors.Wrap(ErrMalformedGoVersionOutput, stdout.String())
}

return goSemver, nil
}
17 changes: 17 additions & 0 deletions pkg/gocli/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package gocli_test

import (
"runtime"
"strings"
"testing"

"github.com/schwarzit/go-template/pkg/gocli"
"github.com/stretchr/testify/require"
)

func Test_Semver(t *testing.T) {
version, err := gocli.Semver()
require.NoError(t, err)
// check that the version this test was build with matches the go version provided by goexec.Semver.
require.Equal(t, strings.TrimPrefix(runtime.Version(), "go"), version.String())
}
55 changes: 40 additions & 15 deletions pkg/gotemplate/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,30 @@ import (
"fmt"
"io/fs"
"os"
"os/exec"
"path"
"reflect"
"strconv"
"strings"
"text/template"

"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"

gotemplate "github.com/schwarzit/go-template"
ownexec "github.com/schwarzit/go-template/pkg/exec"
"github.com/schwarzit/go-template/pkg/gocli"
)

const minGoVersion = "1.15"

var (
ErrAlreadyExists = errors.New("already exists")
ErrParameterNotSet = errors.New("parameter not set")
ErrMalformedInput = errors.New("malformed input")
ErrParameterSet = errors.New("parameter set but has no effect in this context")

minGoVersionSemver = semver.MustParse(minGoVersion)
)

type ErrTypeMismatch struct {
Expand Down Expand Up @@ -224,32 +230,51 @@ func (gt *GT) InitNewProject(opts *NewRepositoryOptions) (err error) {
}

gt.printProgressf("Initializing git and Go modules...")
if err := initRepo(targetDir, opts.OptionValues.Base["moduleName"].(string)); err != nil {
return err
}
gt.initRepo(targetDir, opts.OptionValues.Base["moduleName"].(string))

return nil
}

func initRepo(targetDir, moduleName string) error {
gitInit := exec.Command("git", "init")
gitInit.Dir = targetDir
func (gt *GT) initRepo(targetDir, moduleName string) {
commandGroups := []ownexec.CommandGroup{
{
Commands: []ownexec.Command{
ownexec.NewExecCmd([]string{"git", "init"}, ownexec.WithTargetDir(targetDir)),
},
},
{
PreRun: checkGoVersion,
Commands: []ownexec.Command{
ownexec.NewExecCmd([]string{"go", "mod", "init", moduleName}, ownexec.WithTargetDir(targetDir)),
ownexec.NewExecCmd([]string{"go", "mod", "tidy"}, ownexec.WithTargetDir(targetDir)),
},
},
}

if err := gitInit.Run(); err != nil {
return err
failedCGs := 0
for _, cg := range commandGroups {
if err := cg.Run(); err != nil {
gt.printWarningf(err.Error())
failedCGs++
}
}

goModInit := exec.Command("go", "mod", "init", moduleName)
goModInit.Dir = targetDir
if failedCGs > 0 {
gt.printWarningf("one or more initialization steps failed, pls see warnings for more info.")
}
}

if err := goModInit.Run(); err != nil {
func checkGoVersion() error {
goSemver, err := gocli.Semver()
if err != nil {
return err
}

goModTidy := exec.Command("go", "mod", "tidy")
goModTidy.Dir = targetDir
if goSemver.LessThan(minGoVersionSemver) {
return fmt.Errorf("go version %s is not supported, requires at least %s", goSemver.String(), minGoVersionSemver.String())
}

return goModTidy.Run()
return nil
}

func postHook(options *Options, optionValues *OptionValues, targetDir string) error {
Expand Down

0 comments on commit ed68423

Please sign in to comment.