Add support for "go run" style execution (#3503)

This commit is contained in:
Evan Boyle 2019-11-14 09:25:55 -08:00 committed by GitHub
parent 95ce3e2567
commit 5ae4149af5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 100 additions and 5 deletions

View file

@ -3,6 +3,9 @@ CHANGELOG
## HEAD (Unreleased)
- Support for a `go run` style workflow. Building or installing a pulumi program written in go is
now optional. [3503](https://github.com/pulumi/pulumi/pull/3503)
- Re-apply "propagate resource inputs to resource state during preview, including first-class unknown values." The new
set of changes have additional fixes to ensure backwards compatibility with earlier code. This allows the preview to
better estimate the state of a resource after an update, including property values that were populated using defaults

View file

@ -1608,6 +1608,12 @@ func (pt *programTester) prepareGoProject(projinfo *engine.Projinfo) error {
if err != nil {
return err
}
// skip building if the 'go run' invocation path is requested.
if !pt.opts.RunBuild {
return nil
}
outBin := filepath.Join(gopath, "bin", string(projinfo.Proj.Name))
return pt.runCommand("go-build", []string{goBin, "build", "-o", outBin, "."}, cwd)
}

View file

@ -92,6 +92,8 @@ func (host *goLanguageHost) GetRequiredPlugins(ctx context.Context,
return &pulumirpc.GetRequiredPluginsResponse{}, nil
}
const unableToFindProgramTemplate = "unable to find program: %s"
// findProgram attempts to find the needed program in various locations on the
// filesystem, eventually resorting to searching in $PATH.
func findProgram(program string) (string, error) {
@ -123,7 +125,7 @@ func findProgram(program string) (string, error) {
return fullPath, nil
}
return "", errors.Errorf("unable to find program: %s", program)
return "", errors.Errorf(unableToFindProgramTemplate, program)
}
// RPC endpoint for LanguageRuntimeServer::Run
@ -135,18 +137,51 @@ func (host *goLanguageHost) Run(ctx context.Context, req *pulumirpc.RunRequest)
return nil, errors.Wrap(err, "failed to prepare environment")
}
// by default we try to run a named executable on the path, but we will fallback to 'go run' style execution
goRunInvoke := false
// The program to execute is simply the name of the project. This ensures good Go toolability, whereby
// you can simply run `go install .` to build a Pulumi program prior to running it, among other benefits.
// For ease of use, if we don't find a pre-built program, we attempt to invoke via 'go run' on behalf of the user.
program, err := findProgram(req.GetProject())
if err != nil {
return nil, errors.Wrap(err, "problem executing program (could not run language executor)")
const message = "problem executing program (could not run language executor)"
if err.Error() == fmt.Sprintf(unableToFindProgramTemplate, req.GetProject()) {
logging.V(5).Infof("Unable to find program %s in $PATH, attempting invocation via 'go run'", program)
program, err = findProgram("go")
if err != nil {
return nil, errors.Wrap(err, message)
}
goRunInvoke = true
} else {
return nil, errors.Wrap(err, message)
}
}
logging.V(5).Infof("language host launching process: %s", program)
// Now simply spawn a process to execute the requested program, wiring up stdout/stderr directly.
var errResult string
cmd := exec.Command(program)
var cmd *exec.Cmd
if goRunInvoke {
cwd, err := os.Getwd()
if err != nil {
return nil, errors.Wrap(err, "unable to get current working directory")
}
goFileSearchPattern := filepath.Join(cwd, "*.go")
if matches, err := filepath.Glob(goFileSearchPattern); err != nil || len(matches) == 0 {
return nil, errors.Errorf("Failed to find go files for 'go run' matching %s", goFileSearchPattern)
}
args := []string{"run", cwd}
// go run $cwd
cmd = exec.Command(program, args...)
} else {
cmd = exec.Command(program)
}
cmd.Env = env
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

View file

@ -0,0 +1,3 @@
name: emptygorun
description: An empty Go Pulumi program.
runtime: go

View file

@ -0,0 +1,13 @@
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
package main
import (
"github.com/pulumi/pulumi/sdk/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
return nil
})
}

View file

@ -0,0 +1,4 @@
name: emptygorunmain
description: An empty Go Pulumi program.
runtime: go
main: ../gorun_main_src/

View file

@ -0,0 +1,13 @@
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
package main
import (
"github.com/pulumi/pulumi/sdk/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
return nil
})
}

View file

@ -76,10 +76,27 @@ func TestEmptyPython(t *testing.T) {
})
}
// TestEmptyGo simply tests that we can run an empty Go project.
// TestEmptyGo simply tests that we can build and run an empty Go project.
func TestEmptyGo(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("empty", "go"),
Dir: filepath.Join("empty", "go"),
Quick: true,
RunBuild: true,
})
}
// TestEmptyGoRun exercises the 'go run' invocation path that doesn't require an explicit build step.
func TestEmptyGoRun(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("empty", "gorun"),
Quick: true,
})
}
// TestEmptyGoRunMain exercises the 'go run' invocation path with a 'main' entrypoint specified in Pulumi.yml
func TestEmptyGoRunMain(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("empty", "gorun_main"),
Quick: true,
})
}
@ -959,6 +976,7 @@ func TestConfigBasicGo(t *testing.T) {
{Key: "tokens[0]", Value: "shh", Path: true, Secret: true},
{Key: "foo.bar", Value: "don't tell", Path: true, Secret: true},
},
RunBuild: true,
})
}