From f3b41e038e350dc74f882be031b40cbb926e8dcd Mon Sep 17 00:00:00 2001 From: Vivek Lakshmanan Date: Wed, 11 Nov 2020 15:01:38 -0800 Subject: [PATCH 1/3] Pulumi-language-go now checks go version is atleast 1.14.0 --- sdk/go/pulumi-language-go/main.go | 53 +++++++++++++++++++++----- sdk/go/pulumi-language-go/main_test.go | 45 ++++++++++++++++++++++ 2 files changed, 88 insertions(+), 10 deletions(-) diff --git a/sdk/go/pulumi-language-go/main.go b/sdk/go/pulumi-language-go/main.go index 16d16ee76..1fd4f7ddb 100644 --- a/sdk/go/pulumi-language-go/main.go +++ b/sdk/go/pulumi-language-go/main.go @@ -177,9 +177,8 @@ func (m *modInfo) getPlugin() (*pulumirpc.PluginDependency, error) { } // GetRequiredPlugins computes the complete set of anticipated plugins required by a program. -// We're lenient here as this relies on the `go list` command and the use of modules. -// If the consumer insists on using some other form of dependency management tool like -// dep or glide, the list command fails with "go list -m: not using modules" +// We strictly enforce the requirement for go modules support and go 1.14.0+ and fail with +// an appropriate message if these requirements are not met. func (host *goLanguageHost) GetRequiredPlugins(ctx context.Context, req *pulumirpc.GetRequiredPluginsRequest) (*pulumirpc.GetRequiredPluginsResponse, error) { @@ -190,15 +189,23 @@ func (host *goLanguageHost) GetRequiredPlugins(ctx context.Context, return nil, errors.Wrap(err, "couldn't find go binary") } - // don't wire up stderr so non-module users don't see error output from list - cmd := exec.Command(gobin, "list", "-m", "-json", "-mod=mod", "all") - cmd.Env = os.Environ() - + cmd := exec.Command(gobin, "version") stdout, err := cmd.Output() if err != nil { - // will err if the project isn't using modules - logging.V(5).Infof("GetRequiredPlugins: Error discovering plugin requirements: %s", err.Error()) - return &pulumirpc.GetRequiredPluginsResponse{}, nil + return nil, fmt.Errorf("failed to determine go version: %w", err) + } + rawVersion := string(stdout) + if err = checkMinimumGoVersion(rawVersion); err != nil { + return nil, err + } + + // don't wire up stderr so non-module users don't see error output from list + cmd = exec.Command(gobin, "list", "-m", "-json", "-mod=mod", "all") + cmd.Env = os.Environ() + stdout, err = cmd.Output() + if err != nil { + logging.V(5).Infof("GetRequiredPlugins: Error discovering plugin requirements using go modules: %s", err.Error()) + return nil, fmt.Errorf("failure discovering program dependencies: %w", err) } plugins := []*pulumirpc.PluginDependency{} @@ -233,6 +240,32 @@ func (host *goLanguageHost) GetRequiredPlugins(ctx context.Context, }, nil } +var minGoVersion = semver.MustParse("1.14.0") + +// checkMinimumGoVersion checks to make sure we are running at least go 1.14.0 +// expected format of goVersionOutput: go version go +func checkMinimumGoVersion(goVersionOutput string) error { + split := strings.Split(goVersionOutput, " ") + if len(split) <= 2 { + return fmt.Errorf("unexpected format for go version output: \"%s\"", goVersionOutput) + + } + version := strings.TrimSpace(split[2]) + if strings.HasPrefix(version, "go") { + version = version[2:] + } + + currVersion, err := semver.ParseTolerant(version) + if err != nil { + return fmt.Errorf("parsing go version failed: %w", err) + } + + if currVersion.LT(minGoVersion) { + return fmt.Errorf("go version must be %s or higher (%s detected)", minGoVersion.String(), version) + } + return nil +} + // RPC endpoint for LanguageRuntimeServer::Run func (host *goLanguageHost) Run(ctx context.Context, req *pulumirpc.RunRequest) (*pulumirpc.RunResponse, error) { // Create the environment we'll use to run the process. This is how we pass the RunInfo to the actual diff --git a/sdk/go/pulumi-language-go/main_test.go b/sdk/go/pulumi-language-go/main_test.go index 49e35ae5f..e13da15f5 100644 --- a/sdk/go/pulumi-language-go/main_test.go +++ b/sdk/go/pulumi-language-go/main_test.go @@ -1,6 +1,8 @@ package main import ( + "errors" + "github.com/stretchr/testify/require" "testing" "github.com/stretchr/testify/assert" @@ -64,3 +66,46 @@ func TestGetPlugin(t *testing.T) { assert.Equal(t, nonZeroPatchPlugin.Name, "kubernetes") assert.Equal(t, nonZeroPatchPlugin.Version, "v1.5.8") } + +func Test_checkMinimumGoVersion(t *testing.T) { + tests := []struct { + name string + goVersionOutput string + err error + }{ + { + name: "ExactVersion", + goVersionOutput: "go version go1.14.0 darwin/amd64", + }, + { + name: "NewerVersion", + goVersionOutput: "go version go1.15.1 darwin/amd64", + }, + { + name: "OlderGoVersion", + goVersionOutput: "go version go1.13.8 linux/amd64", + err: errors.New("go version must be 1.14.0 or higher (1.13.8 detected)"), + }, + { + name: "MalformedVersion", + goVersionOutput: "go version xyz", + err: errors.New("parsing go version failed: Invalid character(s) found in major number \"xyz\""), + }, + { + name: "GarbageVersionOutput", + goVersionOutput: "gobble gobble", + err: errors.New("unexpected format for go version output: \"gobble gobble\""), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := checkMinimumGoVersion(tt.goVersionOutput) + if err != nil { + require.Error(t, err) + assert.EqualError(t, err, tt.err.Error()) + return + } + require.NoError(t, err) + }) + } +} From bcb9d0f4c87ae89cf1e6824090c31dc5d4099e05 Mon Sep 17 00:00:00 2001 From: Vivek Lakshmanan Date: Wed, 11 Nov 2020 15:37:33 -0800 Subject: [PATCH 2/3] Refactor to add version check to pulumi new as well --- CHANGELOG.md | 3 ++ pkg/cmd/pulumi/new.go | 11 +++-- sdk/go/common/util/goversion/version.go | 45 +++++++++++++++++ sdk/go/common/util/goversion/version_test.go | 51 ++++++++++++++++++++ sdk/go/pulumi-language-go/main.go | 47 ++++-------------- sdk/go/pulumi-language-go/main_test.go | 45 ----------------- 6 files changed, 116 insertions(+), 86 deletions(-) create mode 100644 sdk/go/common/util/goversion/version.go create mode 100644 sdk/go/common/util/goversion/version_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ff5addd1..60d18edda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ CHANGELOG - Support python 3.9 on Windows. [#5739](https://github.com/pulumi/pulumi/pull/5739) +- `pulumi-language-go` and `pulumi new` now explicitly requires Go 1.14.0. + [#5741](https://github.com/pulumi/pulumi/pull/5741) + ## 2.13.2 (2020-11-06) - Fix a bug that was causing errors when (de)serializing custom resources. diff --git a/pkg/cmd/pulumi/new.go b/pkg/cmd/pulumi/new.go index d650eb044..a90fb2974 100644 --- a/pkg/cmd/pulumi/new.go +++ b/pkg/cmd/pulumi/new.go @@ -43,6 +43,7 @@ import ( "github.com/pulumi/pulumi/sdk/v2/go/common/util/cmdutil" "github.com/pulumi/pulumi/sdk/v2/go/common/util/contract" "github.com/pulumi/pulumi/sdk/v2/go/common/util/executable" + "github.com/pulumi/pulumi/sdk/v2/go/common/util/goversion" "github.com/pulumi/pulumi/sdk/v2/go/common/util/logging" "github.com/pulumi/pulumi/sdk/v2/go/common/workspace" "github.com/pulumi/pulumi/sdk/v2/nodejs/npm" @@ -575,8 +576,7 @@ func installDependencies(proj *workspace.Project, root string) error { return dotnetInstallDependenciesAndBuild(proj, root) } else if strings.EqualFold(proj.Runtime.Name(), "go") { if err := goInstallDependencies(); err != nil { - return errors.Wrapf(err, "`go mod download` failed to install dependencies; rerun manually to try again, "+ - "then run 'pulumi up' to perform an initial deployment") + return err } } @@ -649,12 +649,17 @@ func goInstallDependencies() error { return err } + if err = goversion.CheckMinimumGoVersion(gobin); err != nil { + return err + } + cmd := exec.Command(gobin, "mod", "download") cmd.Env = os.Environ() cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr if err := cmd.Run(); err != nil { - return err + return errors.Wrapf(err, "`go mod download` failed to install dependencies; rerun manually to try again, "+ + "then run 'pulumi up' to perform an initial deployment") } fmt.Println("Finished installing dependencies") diff --git a/sdk/go/common/util/goversion/version.go b/sdk/go/common/util/goversion/version.go new file mode 100644 index 000000000..12959fb2b --- /dev/null +++ b/sdk/go/common/util/goversion/version.go @@ -0,0 +1,45 @@ +package goversion + +import ( + "github.com/blang/semver" + "github.com/pkg/errors" + "os/exec" + "strings" +) + +var minGoVersion = semver.MustParse("1.14.0") + +// CheckMinimumGoVersion checks to make sure we are running at least minGoVersion +func CheckMinimumGoVersion(gobin string) error { + cmd := exec.Command(gobin, "version") + stdout, err := cmd.Output() + if err != nil { + return errors.Wrap(err, "determining go version") + } + + return checkMinimumGoVersion(string(stdout)) +} + +// checkMinimumGoVersion checks to make sure we are running at least go 1.14.0 +// expected format of goVersionOutput: go version go +func checkMinimumGoVersion(goVersionOutput string) error { + split := strings.Split(goVersionOutput, " ") + if len(split) <= 2 { + return errors.Errorf("unexpected format for go version output: \"%s\"", goVersionOutput) + + } + version := strings.TrimSpace(split[2]) + if strings.HasPrefix(version, "go") { + version = version[2:] + } + + currVersion, err := semver.ParseTolerant(version) + if err != nil { + return errors.Wrap(err, "parsing go version") + } + + if currVersion.LT(minGoVersion) { + return errors.Errorf("go version must be %s or higher (%s detected)", minGoVersion.String(), version) + } + return nil +} diff --git a/sdk/go/common/util/goversion/version_test.go b/sdk/go/common/util/goversion/version_test.go new file mode 100644 index 000000000..d2cfed2ad --- /dev/null +++ b/sdk/go/common/util/goversion/version_test.go @@ -0,0 +1,51 @@ +package goversion + +import ( + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func Test_checkMinimumGoVersion(t *testing.T) { + tests := []struct { + name string + goVersionOutput string + err error + }{ + { + name: "ExactVersion", + goVersionOutput: "go version go1.14.0 darwin/amd64", + }, + { + name: "NewerVersion", + goVersionOutput: "go version go1.15.1 darwin/amd64", + }, + { + name: "OlderGoVersion", + goVersionOutput: "go version go1.13.8 linux/amd64", + err: errors.New("go version must be 1.14.0 or higher (1.13.8 detected)"), + }, + { + name: "MalformedVersion", + goVersionOutput: "go version xyz", + err: errors.New("parsing go version failed: Invalid character(s) found in major number \"xyz\""), + }, + { + name: "GarbageVersionOutput", + goVersionOutput: "gobble gobble", + err: errors.New("unexpected format for go version output: \"gobble gobble\""), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := checkMinimumGoVersion(tt.goVersionOutput) + if err != nil { + require.Error(t, err) + assert.EqualError(t, err, tt.err.Error()) + return + } + require.NoError(t, err) + }) + } +} diff --git a/sdk/go/pulumi-language-go/main.go b/sdk/go/pulumi-language-go/main.go index 1fd4f7ddb..16025060c 100644 --- a/sdk/go/pulumi-language-go/main.go +++ b/sdk/go/pulumi-language-go/main.go @@ -35,6 +35,7 @@ import ( "github.com/pulumi/pulumi/sdk/v2/go/common/util/buildutil" "github.com/pulumi/pulumi/sdk/v2/go/common/util/cmdutil" "github.com/pulumi/pulumi/sdk/v2/go/common/util/executable" + "github.com/pulumi/pulumi/sdk/v2/go/common/util/goversion" "github.com/pulumi/pulumi/sdk/v2/go/common/util/logging" "github.com/pulumi/pulumi/sdk/v2/go/common/util/rpcutil" "github.com/pulumi/pulumi/sdk/v2/go/common/version" @@ -177,8 +178,10 @@ func (m *modInfo) getPlugin() (*pulumirpc.PluginDependency, error) { } // GetRequiredPlugins computes the complete set of anticipated plugins required by a program. -// We strictly enforce the requirement for go modules support and go 1.14.0+ and fail with -// an appropriate message if these requirements are not met. +// We're lenient here as this relies on the `go list` command and the use of modules. +// If the consumer insists on using some other form of dependency management tool like +// dep or glide, the list command fails with "go list -m: not using modules". +// However, we do enforce that go 1.14.0 or higher is installed. func (host *goLanguageHost) GetRequiredPlugins(ctx context.Context, req *pulumirpc.GetRequiredPluginsRequest) (*pulumirpc.GetRequiredPluginsResponse, error) { @@ -189,23 +192,17 @@ func (host *goLanguageHost) GetRequiredPlugins(ctx context.Context, return nil, errors.Wrap(err, "couldn't find go binary") } - cmd := exec.Command(gobin, "version") - stdout, err := cmd.Output() - if err != nil { - return nil, fmt.Errorf("failed to determine go version: %w", err) - } - rawVersion := string(stdout) - if err = checkMinimumGoVersion(rawVersion); err != nil { + if err = goversion.CheckMinimumGoVersion(gobin); err != nil { return nil, err } // don't wire up stderr so non-module users don't see error output from list - cmd = exec.Command(gobin, "list", "-m", "-json", "-mod=mod", "all") + cmd := exec.Command(gobin, "list", "-m", "-json", "-mod=mod", "all") cmd.Env = os.Environ() - stdout, err = cmd.Output() + stdout, err := cmd.Output() if err != nil { logging.V(5).Infof("GetRequiredPlugins: Error discovering plugin requirements using go modules: %s", err.Error()) - return nil, fmt.Errorf("failure discovering program dependencies: %w", err) + return &pulumirpc.GetRequiredPluginsResponse{}, nil } plugins := []*pulumirpc.PluginDependency{} @@ -240,32 +237,6 @@ func (host *goLanguageHost) GetRequiredPlugins(ctx context.Context, }, nil } -var minGoVersion = semver.MustParse("1.14.0") - -// checkMinimumGoVersion checks to make sure we are running at least go 1.14.0 -// expected format of goVersionOutput: go version go -func checkMinimumGoVersion(goVersionOutput string) error { - split := strings.Split(goVersionOutput, " ") - if len(split) <= 2 { - return fmt.Errorf("unexpected format for go version output: \"%s\"", goVersionOutput) - - } - version := strings.TrimSpace(split[2]) - if strings.HasPrefix(version, "go") { - version = version[2:] - } - - currVersion, err := semver.ParseTolerant(version) - if err != nil { - return fmt.Errorf("parsing go version failed: %w", err) - } - - if currVersion.LT(minGoVersion) { - return fmt.Errorf("go version must be %s or higher (%s detected)", minGoVersion.String(), version) - } - return nil -} - // RPC endpoint for LanguageRuntimeServer::Run func (host *goLanguageHost) Run(ctx context.Context, req *pulumirpc.RunRequest) (*pulumirpc.RunResponse, error) { // Create the environment we'll use to run the process. This is how we pass the RunInfo to the actual diff --git a/sdk/go/pulumi-language-go/main_test.go b/sdk/go/pulumi-language-go/main_test.go index e13da15f5..49e35ae5f 100644 --- a/sdk/go/pulumi-language-go/main_test.go +++ b/sdk/go/pulumi-language-go/main_test.go @@ -1,8 +1,6 @@ package main import ( - "errors" - "github.com/stretchr/testify/require" "testing" "github.com/stretchr/testify/assert" @@ -66,46 +64,3 @@ func TestGetPlugin(t *testing.T) { assert.Equal(t, nonZeroPatchPlugin.Name, "kubernetes") assert.Equal(t, nonZeroPatchPlugin.Version, "v1.5.8") } - -func Test_checkMinimumGoVersion(t *testing.T) { - tests := []struct { - name string - goVersionOutput string - err error - }{ - { - name: "ExactVersion", - goVersionOutput: "go version go1.14.0 darwin/amd64", - }, - { - name: "NewerVersion", - goVersionOutput: "go version go1.15.1 darwin/amd64", - }, - { - name: "OlderGoVersion", - goVersionOutput: "go version go1.13.8 linux/amd64", - err: errors.New("go version must be 1.14.0 or higher (1.13.8 detected)"), - }, - { - name: "MalformedVersion", - goVersionOutput: "go version xyz", - err: errors.New("parsing go version failed: Invalid character(s) found in major number \"xyz\""), - }, - { - name: "GarbageVersionOutput", - goVersionOutput: "gobble gobble", - err: errors.New("unexpected format for go version output: \"gobble gobble\""), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := checkMinimumGoVersion(tt.goVersionOutput) - if err != nil { - require.Error(t, err) - assert.EqualError(t, err, tt.err.Error()) - return - } - require.NoError(t, err) - }) - } -} From b2e256242760aee541bec83338d9aeb05ca0306c Mon Sep 17 00:00:00 2001 From: Vivek Lakshmanan Date: Thu, 12 Nov 2020 08:54:52 -0800 Subject: [PATCH 3/3] Fix test --- sdk/go/common/util/goversion/version_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/go/common/util/goversion/version_test.go b/sdk/go/common/util/goversion/version_test.go index d2cfed2ad..03f0451cd 100644 --- a/sdk/go/common/util/goversion/version_test.go +++ b/sdk/go/common/util/goversion/version_test.go @@ -29,7 +29,7 @@ func Test_checkMinimumGoVersion(t *testing.T) { { name: "MalformedVersion", goVersionOutput: "go version xyz", - err: errors.New("parsing go version failed: Invalid character(s) found in major number \"xyz\""), + err: errors.New("parsing go version: Invalid character(s) found in major number \"xyz\""), }, { name: "GarbageVersionOutput",