Revert "GetRequiredPlugins for Go (#3830)" (#4034)

This reverts commit 3a2890c0cd.
This commit is contained in:
Evan Boyle 2020-03-06 17:50:18 -08:00 committed by GitHub
parent 3a2890c0cd
commit 81b6afa3c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 153 deletions

View file

@ -2,12 +2,10 @@ CHANGELOG
=========
## HEAD (Unreleased)
- Fix Kubernetes YAML parsing error in .NET.
* Fix Kubernetes YAML parsing error in .NET.
[#4023](https://github.com/pulumi/pulumi/pull/4023)
- Avoid projects beginning with `Pulumi` to stop cyclic imports
[#4013](https://github.com/pulumi/pulumi/pull/4013)
- Add support for plugin acquisition for Go programs
[#3830](https://github.com/pulumi/pulumi/pull/3830)
## 1.12.0 (2020-03-04)
- Avoid Configuring providers which are not used during preview.

View file

@ -600,7 +600,7 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource) error {
fmt.Fprintf(w, "// Get%[1]s gets an existing %[1]s resource's state with the given name, ID, and optional\n", name)
fmt.Fprintf(w, "// state properties that are used to uniquely qualify the lookup (nil if not required).\n")
fmt.Fprintf(w, "func Get%s(ctx *pulumi.Context,\n", name)
fmt.Fprintf(w, "\tname string, id pulumi.StringInput, state *%[1]sState, opts ...pulumi.ResourceOption) (*%[1]s, error) {\n", name)
fmt.Fprintf(w, "\tname string, id pulumi.IDInput, state *%[1]sState, opts ...pulumi.ResourceOption) (*%[1]s, error) {\n", name)
fmt.Fprintf(w, "\tvar resource %s\n", name)
fmt.Fprintf(w, "\terr := ctx.ReadResource(\"%s\", name, id, state, &resource, opts...)\n", r.Token)
fmt.Fprintf(w, "\tif err != nil {\n")
@ -720,7 +720,7 @@ func (pkg *pkgContext) genType(w io.Writer, obj *schema.ObjectType) {
pkg.genOutputTypes(w, obj, pkg.details(obj))
}
func (pkg *pkgContext) genTypeRegistrations(w io.Writer, types []*schema.ObjectType) {
func (pkg *pkgContext) genInitFn(w io.Writer, types []*schema.ObjectType) {
fmt.Fprintf(w, "func init() {\n")
for _, obj := range types {
name, details := pkg.tokenToType(obj.Token), pkg.details(obj)
@ -879,12 +879,6 @@ func (pkg *pkgContext) genConfig(w io.Writer, variables []*schema.Property) erro
return nil
}
func (pkg *pkgContext) genPackageRegistration(w io.Writer) {
fmt.Fprintf(w, "func init() {\n")
fmt.Fprintf(w, "\tpulumi.RegisterPackage(pulumi.PackageInfo{Name:\"%s\", Version:\"%s\"})\n", pkg.pkg.Name, pkg.pkg.Version.String())
fmt.Fprintf(w, "}\n")
}
func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error) {
// group resources, types, and functions into Go packages
packages := map[string]*pkgContext{}
@ -998,7 +992,7 @@ func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error
files[relPath] = []byte(contents)
}
name, registerPackage := pkg.Name, pkg.Provider != nil
name := pkg.Name
for _, mod := range pkgMods {
pkg := packages[mod]
@ -1070,21 +1064,11 @@ func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error
pkg.genType(buffer, t)
}
pkg.genTypeRegistrations(buffer, pkg.types)
pkg.genInitFn(buffer, pkg.types)
setFile(path.Join(mod, "pulumiTypes.go"), buffer.String())
}
// Package registration
if registerPackage {
buffer := &bytes.Buffer{}
pkg.genHeader(buffer, []string{"github.com/pulumi/pulumi/sdk/go/pulumi"}, nil)
pkg.genPackageRegistration(buffer)
setFile(path.Join(mod, "pulumiManifest.go"), buffer.String())
}
// Utilities
if pkg.needsUtils {
buffer := &bytes.Buffer{}

View file

@ -36,73 +36,6 @@ import (
pulumirpc "github.com/pulumi/pulumi/sdk/proto/go"
)
const unableToFindProgramTemplate = "unable to find program: %s"
// findExecutable attempts to find the needed executable in various locations on the
// filesystem, eventually resorting to searching in $PATH.
func findExecutable(program string) (string, error) {
// look in the same directory
cwd, err := os.Getwd()
if err != nil {
return "", errors.Wrap(err, "unable to get current working directory")
}
cwdProgram := filepath.Join(cwd, program)
if fileInfo, err := os.Stat(cwdProgram); !os.IsNotExist(err) && !fileInfo.Mode().IsDir() {
logging.V(5).Infof("program %s found in CWD", program)
return cwdProgram, nil
}
// look in $GOPATH/bin
if goPath := os.Getenv("GOPATH"); len(goPath) > 0 {
goPathProgram := filepath.Join(goPath, "bin", program)
if fileInfo, err := os.Stat(goPathProgram); !os.IsNotExist(err) && !fileInfo.Mode().IsDir() {
logging.V(5).Infof("program %s found in $GOPATH/bin", program)
return goPathProgram, nil
}
}
// look in the $PATH somewhere
if fullPath, err := exec.LookPath(program); err == nil {
logging.V(5).Infof("program %s found in $PATH", program)
return fullPath, nil
}
return "", errors.Errorf(unableToFindProgramTemplate, program)
}
func findProgram(project string) (*exec.Cmd, error) {
// 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 := findExecutable(project)
if err == nil {
return exec.Command(program), nil
}
const message = "problem executing program (could not run language executor)"
if err.Error() == fmt.Sprintf(unableToFindProgramTemplate, project) {
logging.V(5).Infof("Unable to find program %s in $PATH, attempting invocation via 'go run'", program)
program, err = findExecutable("go")
}
if err != nil {
return nil, errors.Wrap(err, message)
}
// Fall back to 'go run' style execution
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)
}
return exec.Command(program, "run", cwd), nil
}
// Launches the language host, which in turn fires up an RPC server implementing the LanguageRuntimeServer endpoint.
func main() {
var tracing string
@ -156,33 +89,43 @@ func newLanguageHost(engineAddress, tracing string) pulumirpc.LanguageRuntimeSer
// GetRequiredPlugins computes the complete set of anticipated plugins required by a program.
func (host *goLanguageHost) GetRequiredPlugins(ctx context.Context,
req *pulumirpc.GetRequiredPluginsRequest) (*pulumirpc.GetRequiredPluginsResponse, error) {
return &pulumirpc.GetRequiredPluginsResponse{}, nil
}
cmd, err := findProgram(req.GetProject())
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) {
// look in the same directory
cwd, err := os.Getwd()
if err != nil {
return nil, err
return "", errors.Wrap(err, "unable to get current working directory")
}
cmd.Env = []string{"PULUMI_PLUGINS=true"}
stdout, err := cmd.Output()
if err != nil {
return nil, err
cwdProgram := filepath.Join(cwd, program)
if fileInfo, err := os.Stat(cwdProgram); !os.IsNotExist(err) && !fileInfo.Mode().IsDir() {
logging.V(5).Infof("program %s found in CWD", program)
return cwdProgram, nil
}
var infos map[string][]pulumi.PackageInfo
if err := json.Unmarshal(stdout, &infos); err != nil {
return nil, err
// look in $GOPATH/bin
if goPath := os.Getenv("GOPATH"); len(goPath) > 0 {
goPathProgram := filepath.Join(goPath, "bin", program)
if fileInfo, err := os.Stat(goPathProgram); !os.IsNotExist(err) && !fileInfo.Mode().IsDir() {
logging.V(5).Infof("program %s found in $GOPATH/bin", program)
return goPathProgram, nil
}
}
var plugins []*pulumirpc.PluginDependency
for _, info := range infos["plugins"] {
plugins = append(plugins, &pulumirpc.PluginDependency{
Name: info.Name,
Kind: "resource",
Version: info.Version,
Server: info.Server,
})
// look in the $PATH somewhere
if fullPath, err := exec.LookPath(program); err == nil {
logging.V(5).Infof("program %s found in $PATH", program)
return fullPath, nil
}
return &pulumirpc.GetRequiredPluginsResponse{Plugins: plugins}, nil
return "", errors.Errorf(unableToFindProgramTemplate, program)
}
// RPC endpoint for LanguageRuntimeServer::Run
@ -194,14 +137,54 @@ func (host *goLanguageHost) Run(ctx context.Context, req *pulumirpc.RunRequest)
return nil, errors.Wrap(err, "failed to prepare environment")
}
cmd, err := findProgram(req.GetProject())
if err != nil {
return nil, err
}
cmd.Env = env
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
// 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 {
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
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
if err := cmd.Run(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// If the program ran, but exited with a non-zero error code. This will happen often, since user

View file

@ -27,8 +27,6 @@ import (
"github.com/pulumi/pulumi/pkg/util/contract"
)
var ErrPlugins = errors.New("pulumi: plugins requested")
// A RunOption is used to control the behavior of Run and RunErr.
type RunOption func(*RunInfo)
@ -37,13 +35,8 @@ type RunOption func(*RunInfo)
// If the program fails, the process will be terminated and the function will not return.
func Run(body RunFunc, opts ...RunOption) {
if err := RunErr(body, opts...); err != nil {
if err != ErrPlugins {
fmt.Fprintf(os.Stderr, "error: program failed: %v\n", err)
os.Exit(1)
}
printRequiredPlugins()
os.Exit(0)
fmt.Fprintf(os.Stderr, "error: program failed: %v\n", err)
os.Exit(1)
}
}
@ -53,9 +46,6 @@ func RunErr(body RunFunc, opts ...RunOption) error {
// Parse the info out of environment variables. This is a lame contract with the caller, but helps to keep
// boilerplate to a minimum in the average Pulumi Go program.
info := getEnvInfo()
if info.getPlugins {
return ErrPlugins
}
for _, o := range opts {
o(&info)
@ -131,7 +121,6 @@ type RunInfo struct {
MonitorAddr string
EngineAddr string
Mocks MockResourceMonitor
getPlugins bool
}
// getEnvInfo reads various program information from the process environment.
@ -139,7 +128,6 @@ func getEnvInfo() RunInfo {
// Most of the variables are just strings, and we can read them directly. A few of them require more parsing.
parallel, _ := strconv.Atoi(os.Getenv(EnvParallel))
dryRun, _ := strconv.ParseBool(os.Getenv(EnvDryRun))
getPlugins, _ := strconv.ParseBool(os.Getenv(envPlugins))
var config map[string]string
if cfg := os.Getenv(EnvConfig); cfg != "" {
@ -154,7 +142,6 @@ func getEnvInfo() RunInfo {
DryRun: dryRun,
MonitorAddr: os.Getenv(EnvMonitor),
EngineAddr: os.Getenv(EnvEngine),
getPlugins: getPlugins,
}
}
@ -173,28 +160,4 @@ const (
EnvMonitor = "PULUMI_MONITOR"
// EnvEngine is the envvar used to read the current Pulumi engine RPC address.
EnvEngine = "PULUMI_ENGINE"
// envPlugins is the envvar used to request that the Pulumi program print its set of required plugins and exit.
envPlugins = "PULUMI_PLUGINS"
)
type PackageInfo struct {
Name string `json:"name"`
Version string `json:"version,omitempty"`
Server string `json:"server,omitempty"`
}
var packageRegistry = map[PackageInfo]struct{}{}
func RegisterPackage(info PackageInfo) {
packageRegistry[info] = struct{}{}
}
func printRequiredPlugins() {
plugins := []PackageInfo{}
for info := range packageRegistry {
plugins = append(plugins, info)
}
err := json.NewEncoder(os.Stdout).Encode(map[string]interface{}{"plugins": plugins})
contract.IgnoreError(err)
}