Support Pulumi programs written in Go
This adds rudimentary support for Pulumi programs written in Go. It
is not complete yet but the basic resource registration works.
Note that, stylistically speaking, Go is a bit different from our other
languages. This made it a bit easier to build this initial prototype,
since what we want is actually a rather thin veneer atop our existing
RPC interfaces. The lack of generics, however, adds some friction and
is something I'm continuing to hammer on; this will most likely lead to
little specialized types (e.g. StringOutput) once the dust settles.
There are two primary components:
1) A new language host, `pulumi-language-go`, which is responsible for
communicating with the engine through the usual gRPC interfaces.
Because Go programs are pre-compiled, it very simply loads a binary
with the same name as the project.
2) A client SDK library that Pulumi programs bind against. This exports
the core resource types -- including assets -- properties -- including
output properties -- and configuration.
Most remaining TODOs are marked as such in the code, and this will not
be merged until they have been addressed, and some better tests written.
2018-06-03 19:37:26 +02:00
|
|
|
// Copyright 2016-2018, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2018-09-20 00:22:33 +02:00
|
|
|
"path/filepath"
|
Support Pulumi programs written in Go
This adds rudimentary support for Pulumi programs written in Go. It
is not complete yet but the basic resource registration works.
Note that, stylistically speaking, Go is a bit different from our other
languages. This made it a bit easier to build this initial prototype,
since what we want is actually a rather thin veneer atop our existing
RPC interfaces. The lack of generics, however, adds some friction and
is something I'm continuing to hammer on; this will most likely lead to
little specialized types (e.g. StringOutput) once the dust settles.
There are two primary components:
1) A new language host, `pulumi-language-go`, which is responsible for
communicating with the engine through the usual gRPC interfaces.
Because Go programs are pre-compiled, it very simply loads a binary
with the same name as the project.
2) A client SDK library that Pulumi programs bind against. This exports
the core resource types -- including assets -- properties -- including
output properties -- and configuration.
Most remaining TODOs are marked as such in the code, and this will not
be merged until they have been addressed, and some better tests written.
2018-06-03 19:37:26 +02:00
|
|
|
"syscall"
|
|
|
|
|
|
|
|
pbempty "github.com/golang/protobuf/ptypes/empty"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"google.golang.org/grpc"
|
|
|
|
|
|
|
|
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
|
|
|
"github.com/pulumi/pulumi/pkg/util/logging"
|
|
|
|
"github.com/pulumi/pulumi/pkg/util/rpcutil"
|
|
|
|
"github.com/pulumi/pulumi/pkg/version"
|
|
|
|
"github.com/pulumi/pulumi/sdk/go/pulumi"
|
|
|
|
pulumirpc "github.com/pulumi/pulumi/sdk/proto/go"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Launches the language host, which in turn fires up an RPC server implementing the LanguageRuntimeServer endpoint.
|
|
|
|
func main() {
|
|
|
|
var tracing string
|
|
|
|
flag.StringVar(&tracing, "tracing", "", "Emit tracing to a Zipkin-compatible tracing endpoint")
|
|
|
|
|
|
|
|
flag.Parse()
|
|
|
|
args := flag.Args()
|
|
|
|
logging.InitLogging(false, 0, false)
|
|
|
|
cmdutil.InitTracing("pulumi-language-go", "pulumi-language-go", tracing)
|
|
|
|
|
|
|
|
// Pluck out the engine so we can do logging, etc.
|
|
|
|
if len(args) == 0 {
|
|
|
|
cmdutil.Exit(errors.New("missing required engine RPC address argument"))
|
|
|
|
}
|
|
|
|
engineAddress := args[0]
|
|
|
|
|
|
|
|
// Fire up a gRPC server, letting the kernel choose a free port.
|
|
|
|
port, done, err := rpcutil.Serve(0, nil, []func(*grpc.Server) error{
|
|
|
|
func(srv *grpc.Server) error {
|
|
|
|
host := newLanguageHost(engineAddress, tracing)
|
|
|
|
pulumirpc.RegisterLanguageRuntimeServer(srv, host)
|
|
|
|
return nil
|
|
|
|
},
|
2019-09-16 23:16:43 +02:00
|
|
|
}, nil)
|
Support Pulumi programs written in Go
This adds rudimentary support for Pulumi programs written in Go. It
is not complete yet but the basic resource registration works.
Note that, stylistically speaking, Go is a bit different from our other
languages. This made it a bit easier to build this initial prototype,
since what we want is actually a rather thin veneer atop our existing
RPC interfaces. The lack of generics, however, adds some friction and
is something I'm continuing to hammer on; this will most likely lead to
little specialized types (e.g. StringOutput) once the dust settles.
There are two primary components:
1) A new language host, `pulumi-language-go`, which is responsible for
communicating with the engine through the usual gRPC interfaces.
Because Go programs are pre-compiled, it very simply loads a binary
with the same name as the project.
2) A client SDK library that Pulumi programs bind against. This exports
the core resource types -- including assets -- properties -- including
output properties -- and configuration.
Most remaining TODOs are marked as such in the code, and this will not
be merged until they have been addressed, and some better tests written.
2018-06-03 19:37:26 +02:00
|
|
|
if err != nil {
|
|
|
|
cmdutil.Exit(errors.Wrapf(err, "could not start language host RPC server"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, print out the port so that the spawner knows how to reach us.
|
|
|
|
fmt.Printf("%d\n", port)
|
|
|
|
|
|
|
|
// And finally wait for the server to stop serving.
|
|
|
|
if err := <-done; err != nil {
|
|
|
|
cmdutil.Exit(errors.Wrapf(err, "language host RPC stopped serving"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// goLanguageHost implements the LanguageRuntimeServer interface for use as an API endpoint.
|
|
|
|
type goLanguageHost struct {
|
|
|
|
engineAddress string
|
|
|
|
tracing string
|
|
|
|
}
|
|
|
|
|
|
|
|
func newLanguageHost(engineAddress, tracing string) pulumirpc.LanguageRuntimeServer {
|
|
|
|
return &goLanguageHost{
|
|
|
|
engineAddress: engineAddress,
|
|
|
|
tracing: tracing,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-11-14 18:25:55 +01:00
|
|
|
const unableToFindProgramTemplate = "unable to find program: %s"
|
|
|
|
|
2018-09-20 00:22:33 +02:00
|
|
|
// 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 "", 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() {
|
2018-11-06 00:22:39 +01:00
|
|
|
logging.V(5).Infof("program %s found in CWD", program)
|
2018-09-20 00:22:33 +02:00
|
|
|
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() {
|
2018-11-06 00:22:39 +01:00
|
|
|
logging.V(5).Infof("program %s found in $GOPATH/bin", program)
|
2018-09-20 00:22:33 +02:00
|
|
|
return goPathProgram, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// look in the $PATH somewhere
|
|
|
|
if fullPath, err := exec.LookPath(program); err == nil {
|
2018-11-06 00:22:39 +01:00
|
|
|
logging.V(5).Infof("program %s found in $PATH", program)
|
2018-09-20 00:22:33 +02:00
|
|
|
return fullPath, nil
|
|
|
|
}
|
|
|
|
|
2019-11-14 18:25:55 +01:00
|
|
|
return "", errors.Errorf(unableToFindProgramTemplate, program)
|
2018-09-20 00:22:33 +02:00
|
|
|
}
|
|
|
|
|
Support Pulumi programs written in Go
This adds rudimentary support for Pulumi programs written in Go. It
is not complete yet but the basic resource registration works.
Note that, stylistically speaking, Go is a bit different from our other
languages. This made it a bit easier to build this initial prototype,
since what we want is actually a rather thin veneer atop our existing
RPC interfaces. The lack of generics, however, adds some friction and
is something I'm continuing to hammer on; this will most likely lead to
little specialized types (e.g. StringOutput) once the dust settles.
There are two primary components:
1) A new language host, `pulumi-language-go`, which is responsible for
communicating with the engine through the usual gRPC interfaces.
Because Go programs are pre-compiled, it very simply loads a binary
with the same name as the project.
2) A client SDK library that Pulumi programs bind against. This exports
the core resource types -- including assets -- properties -- including
output properties -- and configuration.
Most remaining TODOs are marked as such in the code, and this will not
be merged until they have been addressed, and some better tests written.
2018-06-03 19:37:26 +02:00
|
|
|
// 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
|
|
|
|
// Go program runtime, to avoid needing any sort of program interface other than just a main entrypoint.
|
|
|
|
env, err := host.constructEnv(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to prepare environment")
|
|
|
|
}
|
|
|
|
|
2019-11-14 18:25:55 +01:00
|
|
|
// by default we try to run a named executable on the path, but we will fallback to 'go run' style execution
|
|
|
|
goRunInvoke := false
|
|
|
|
|
Support Pulumi programs written in Go
This adds rudimentary support for Pulumi programs written in Go. It
is not complete yet but the basic resource registration works.
Note that, stylistically speaking, Go is a bit different from our other
languages. This made it a bit easier to build this initial prototype,
since what we want is actually a rather thin veneer atop our existing
RPC interfaces. The lack of generics, however, adds some friction and
is something I'm continuing to hammer on; this will most likely lead to
little specialized types (e.g. StringOutput) once the dust settles.
There are two primary components:
1) A new language host, `pulumi-language-go`, which is responsible for
communicating with the engine through the usual gRPC interfaces.
Because Go programs are pre-compiled, it very simply loads a binary
with the same name as the project.
2) A client SDK library that Pulumi programs bind against. This exports
the core resource types -- including assets -- properties -- including
output properties -- and configuration.
Most remaining TODOs are marked as such in the code, and this will not
be merged until they have been addressed, and some better tests written.
2018-06-03 19:37:26 +02:00
|
|
|
// 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.
|
2019-11-14 18:25:55 +01:00
|
|
|
// 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.
|
2018-09-20 00:22:33 +02:00
|
|
|
program, err := findProgram(req.GetProject())
|
|
|
|
if err != nil {
|
2019-11-14 18:25:55 +01:00
|
|
|
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)
|
|
|
|
}
|
2018-09-20 00:22:33 +02:00
|
|
|
}
|
|
|
|
|
2018-11-06 00:22:39 +01:00
|
|
|
logging.V(5).Infof("language host launching process: %s", program)
|
Support Pulumi programs written in Go
This adds rudimentary support for Pulumi programs written in Go. It
is not complete yet but the basic resource registration works.
Note that, stylistically speaking, Go is a bit different from our other
languages. This made it a bit easier to build this initial prototype,
since what we want is actually a rather thin veneer atop our existing
RPC interfaces. The lack of generics, however, adds some friction and
is something I'm continuing to hammer on; this will most likely lead to
little specialized types (e.g. StringOutput) once the dust settles.
There are two primary components:
1) A new language host, `pulumi-language-go`, which is responsible for
communicating with the engine through the usual gRPC interfaces.
Because Go programs are pre-compiled, it very simply loads a binary
with the same name as the project.
2) A client SDK library that Pulumi programs bind against. This exports
the core resource types -- including assets -- properties -- including
output properties -- and configuration.
Most remaining TODOs are marked as such in the code, and this will not
be merged until they have been addressed, and some better tests written.
2018-06-03 19:37:26 +02:00
|
|
|
|
|
|
|
// Now simply spawn a process to execute the requested program, wiring up stdout/stderr directly.
|
|
|
|
var errResult string
|
2019-11-14 18:25:55 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
Support Pulumi programs written in Go
This adds rudimentary support for Pulumi programs written in Go. It
is not complete yet but the basic resource registration works.
Note that, stylistically speaking, Go is a bit different from our other
languages. This made it a bit easier to build this initial prototype,
since what we want is actually a rather thin veneer atop our existing
RPC interfaces. The lack of generics, however, adds some friction and
is something I'm continuing to hammer on; this will most likely lead to
little specialized types (e.g. StringOutput) once the dust settles.
There are two primary components:
1) A new language host, `pulumi-language-go`, which is responsible for
communicating with the engine through the usual gRPC interfaces.
Because Go programs are pre-compiled, it very simply loads a binary
with the same name as the project.
2) A client SDK library that Pulumi programs bind against. This exports
the core resource types -- including assets -- properties -- including
output properties -- and configuration.
Most remaining TODOs are marked as such in the code, and this will not
be merged until they have been addressed, and some better tests written.
2018-06-03 19:37:26 +02:00
|
|
|
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
|
|
|
|
// errors will trigger this. So, the error message should look as nice as possible.
|
|
|
|
if status, stok := exiterr.Sys().(syscall.WaitStatus); stok {
|
|
|
|
err = errors.Errorf("program exited with non-zero exit code: %d", status.ExitStatus())
|
|
|
|
} else {
|
|
|
|
err = errors.Wrapf(exiterr, "program exited unexpectedly")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Otherwise, we didn't even get to run the program. This ought to never happen unless there's
|
|
|
|
// a bug or system condition that prevented us from running the language exec. Issue a scarier error.
|
|
|
|
err = errors.Wrapf(err, "problem executing program (could not run language executor)")
|
|
|
|
}
|
|
|
|
|
|
|
|
errResult = err.Error()
|
|
|
|
}
|
|
|
|
|
|
|
|
return &pulumirpc.RunResponse{Error: errResult}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// constructEnv constructs an environment for a Go progam by enumerating all of the optional and non-optional
|
|
|
|
// arguments present in a RunRequest.
|
|
|
|
func (host *goLanguageHost) constructEnv(req *pulumirpc.RunRequest) ([]string, error) {
|
|
|
|
config, err := host.constructConfig(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-09-21 00:17:18 +02:00
|
|
|
env := os.Environ()
|
Support Pulumi programs written in Go
This adds rudimentary support for Pulumi programs written in Go. It
is not complete yet but the basic resource registration works.
Note that, stylistically speaking, Go is a bit different from our other
languages. This made it a bit easier to build this initial prototype,
since what we want is actually a rather thin veneer atop our existing
RPC interfaces. The lack of generics, however, adds some friction and
is something I'm continuing to hammer on; this will most likely lead to
little specialized types (e.g. StringOutput) once the dust settles.
There are two primary components:
1) A new language host, `pulumi-language-go`, which is responsible for
communicating with the engine through the usual gRPC interfaces.
Because Go programs are pre-compiled, it very simply loads a binary
with the same name as the project.
2) A client SDK library that Pulumi programs bind against. This exports
the core resource types -- including assets -- properties -- including
output properties -- and configuration.
Most remaining TODOs are marked as such in the code, and this will not
be merged until they have been addressed, and some better tests written.
2018-06-03 19:37:26 +02:00
|
|
|
maybeAppendEnv := func(k, v string) {
|
|
|
|
if v != "" {
|
|
|
|
env = append(env, fmt.Sprintf("%s=%s", k, v))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
maybeAppendEnv(pulumi.EnvProject, req.GetProject())
|
|
|
|
maybeAppendEnv(pulumi.EnvStack, req.GetStack())
|
|
|
|
maybeAppendEnv(pulumi.EnvConfig, config)
|
|
|
|
maybeAppendEnv(pulumi.EnvDryRun, fmt.Sprintf("%v", req.GetDryRun()))
|
|
|
|
maybeAppendEnv(pulumi.EnvParallel, fmt.Sprint(req.GetParallel()))
|
|
|
|
maybeAppendEnv(pulumi.EnvMonitor, req.GetMonitorAddress())
|
|
|
|
maybeAppendEnv(pulumi.EnvEngine, host.engineAddress)
|
|
|
|
|
|
|
|
return env, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// constructConfig json-serializes the configuration data given as part of a RunRequest.
|
|
|
|
func (host *goLanguageHost) constructConfig(req *pulumirpc.RunRequest) (string, error) {
|
|
|
|
configMap := req.GetConfig()
|
|
|
|
if configMap == nil {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
configJSON, err := json.Marshal(configMap)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(configJSON), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (host *goLanguageHost) GetPluginInfo(ctx context.Context, req *pbempty.Empty) (*pulumirpc.PluginInfo, error) {
|
|
|
|
return &pulumirpc.PluginInfo{
|
|
|
|
Version: version.Version,
|
|
|
|
}, nil
|
|
|
|
}
|